{
  "components": {
    "headers": {
      "RateLimit-Limit": {
        "description": "Maximum number of requests allowed in the current time window",
        "schema": {
          "example": 1000,
          "type": "integer"
        }
      },
      "RateLimit-Remaining": {
        "description": "Number of requests remaining in the current time window",
        "schema": {
          "example": 997,
          "type": "integer"
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the current rate limit window resets",
        "schema": {
          "example": 58,
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (only present on 429 responses)",
        "schema": {
          "example": 60,
          "type": "integer"
        }
      }
    },
    "parameters": {
      "CampaignIdPath": {
        "in": "path",
        "name": "campaignId",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "CreativeIdPath": {
        "in": "path",
        "name": "creativeId",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ExperimentIdPath": {
        "in": "path",
        "name": "id",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ScreenIdPath": {
        "in": "path",
        "name": "screenId",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "ScreenIdQuery": {
        "description": "Screen identifier",
        "in": "query",
        "name": "screenId",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "SessionIdPath": {
        "in": "path",
        "name": "sessionId",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "ApiKeyUnauthorized": {
        "content": {
          "application/json": {
            "example": {
              "error": "Invalid or missing FEIN API key"
            },
            "schema": {
              "$ref": "#/components/schemas/FeinAuthError"
            }
          }
        },
        "description": "FEIN `X-API-Key` header missing, unknown, or revoked. Emitted by the\nFEIN `requireApiKey` middleware as a flat `{ error }` body — not the\ncanonical envelope.\n"
      },
      "DspForbidden": {
        "content": {
          "application/json": {
            "example": {
              "error": "This API key is missing the required scope \"inventory:read\".",
              "error_code": "missing_scope",
              "granted_scopes": [
                "bid:write",
                "stats:read"
              ],
              "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
              "required_scope": "inventory:read",
              "success": false
            },
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/DspErrorEnvelope"
                },
                {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              ]
            }
          }
        },
        "description": "The key authenticated but is not entitled to this operation.\n\n**Two causes, two different body shapes.** A missing scope comes from the\nscope middleware as the flat DSP envelope with `error_code:\nmissing_scope`, echoing `required_scope` and the `granted_scopes` the key\nactually holds — that is the example below, and it is the common case. A\n`suspended` or `revoked` DSP seat instead comes from the authentication\nmiddleware as the canonical envelope with `code: dsp_access_denied`.\n\nEvery onboarded DSP key carries the default scope set, so a\n`missing_scope` here normally means the key was hand-scoped.\n"
      },
      "DspInventoryRateLimited": {
        "content": {
          "application/json": {
            "example": {
              "error": "DSP inventory rate limit exceeded. Maximum 200 requests per minute.",
              "error_code": "rate_limited",
              "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
              "retry_after_seconds": 60,
              "success": false
            },
            "schema": {
              "$ref": "#/components/schemas/DspErrorEnvelope"
            }
          }
        },
        "description": "DSP inventory rate limit exceeded — 200 requests per rolling 60s window,\nbucketed per DSP seat (falling back to client IP for an unresolved seat).\n\nThis route answers with the `{ success: false }` DSP error envelope, not\nthe canonical one. Honour `Retry-After` and back off exponentially.\n",
        "headers": {
          "RateLimit-Limit": {
            "description": "Requests allowed in the current window",
            "schema": {
              "example": 200,
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "Requests remaining (0 when limited)",
            "schema": {
              "example": 0,
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window resets",
            "schema": {
              "example": 42,
              "type": "integer"
            }
          },
          "Retry-After": {
            "description": "Seconds to wait before retrying",
            "schema": {
              "example": 60,
              "type": "integer"
            }
          }
        }
      },
      "DspUnauthorized": {
        "content": {
          "application/json": {
            "example": {
              "error": {
                "code": "openrtb_auth_failed",
                "doc_url": "https://api.trillboards.com/docs/errors#openrtb_auth_failed",
                "message": "Unauthorized: invalid or missing API key.",
                "param": "x-api-key",
                "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                "type": "authentication_error"
              }
            },
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                },
                {
                  "$ref": "#/components/schemas/DspErrorEnvelope"
                }
              ]
            }
          }
        },
        "description": "Platform API key missing, unknown, or not linked to a DSP seat.\n\nThe key is read from `Authorization: Bearer <key>`, `X-API-Key`, or\n`X-Trillboards-API-Key` (first match wins).\n\n**Two different bodies carry this status on the same route.** The\nauthentication middleware raises the canonical `openrtb_auth_failed`\nerror, shown in the example; the scope middleware's defensive branch\nemits the flat DSP envelope with `error_code: authentication_required`.\nBranch on the status code, and probe for `error` being an object versus\na string before reading into it.\n"
      },
      "Forbidden": {
        "content": {
          "application/json": {
            "example": {
              "error": "Insufficient permissions. Your DSP is in sandbox mode. Contact developers@trillboards.com to upgrade.",
              "success": false
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        },
        "description": "Insufficient permissions. Your API key is valid but does not have the\nrequired scope for this operation, or your DSP is in a restricted state.\n\n**Common causes:**\n- Sandbox DSP attempting a production-only operation\n- API key missing required scope (e.g., `bid:write` for bid submission)\n- DSP account is suspended\n"
      },
      "InternalServerError": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "error": {
                  "example": "Internal server error. Please try again or contact developers@trillboards.com.",
                  "type": "string"
                },
                "request_id": {
                  "description": "Request ID for support reference. Include this when contacting\ndeveloper support.\n",
                  "example": "req_7f8a9b0c-1d2e-3f4a-5b6c-7d8e9f0a1b2c",
                  "type": "string"
                },
                "success": {
                  "example": false,
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          }
        },
        "description": "Internal server error. An unexpected error occurred on the Trillboards side.\nIf this persists, contact developers@trillboards.com with the timestamp and\nany request details.\n"
      },
      "NotFound": {
        "content": {
          "application/json": {
            "example": {
              "error": "Resource not found",
              "success": false
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        },
        "description": "The requested resource was not found. The resource either does not exist\nor does not belong to your DSP.\n"
      },
      "ProgrammaticEventRateLimited": {
        "content": {
          "application/json": {
            "example": {
              "message": "Programmatic event rate limit exceeded.",
              "retryAfter": 60,
              "success": false
            },
            "schema": {
              "$ref": "#/components/schemas/RateLimitError"
            }
          }
        },
        "description": "Programmatic event rate limit exceeded — 300 requests per rolling 60s\nwindow, bucketed on `device_id` from the request body when present and\non client IP otherwise.\n",
        "headers": {
          "RateLimit-Limit": {
            "description": "Requests allowed in the current window",
            "schema": {
              "example": 300,
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "Requests remaining (0 when limited)",
            "schema": {
              "example": 0,
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window resets",
            "schema": {
              "example": 42,
              "type": "integer"
            }
          },
          "Retry-After": {
            "description": "Seconds to wait before retrying",
            "schema": {
              "example": 60,
              "type": "integer"
            }
          }
        }
      },
      "TooManyRequests": {
        "content": {
          "application/json": {
            "schema": {
              "properties": {
                "error": {
                  "example": "Rate limit exceeded. Maximum 1000 requests per minute.",
                  "type": "string"
                },
                "retry_after": {
                  "description": "Seconds to wait before retrying",
                  "example": 42,
                  "type": "integer"
                },
                "success": {
                  "example": false,
                  "type": "boolean"
                }
              },
              "type": "object"
            }
          }
        },
        "description": "Rate limit exceeded. Wait for the period indicated by the `Retry-After`\nheader before retrying. Implement exponential backoff for production\nreliability.\n",
        "headers": {
          "RateLimit-Limit": {
            "description": "Maximum requests allowed in the current window",
            "schema": {
              "example": 1000,
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "Requests remaining (always 0 when rate limited)",
            "schema": {
              "example": 0,
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the rate limit window resets",
            "schema": {
              "example": 42,
              "type": "integer"
            }
          },
          "Retry-After": {
            "description": "Seconds to wait before retrying",
            "schema": {
              "example": 42,
              "type": "integer"
            }
          }
        }
      },
      "TrackingRateLimited": {
        "content": {
          "application/json": {
            "example": {
              "message": "OpenRTB tracking rate limit exceeded.",
              "retryAfter": 60,
              "success": false
            },
            "schema": {
              "$ref": "#/components/schemas/RateLimitError"
            }
          }
        },
        "description": "OpenRTB tracking rate limit exceeded — 300 requests per rolling 60s\nwindow by default, bucketed per partner SDK device (falling back to\nclient IP). The ceiling is a live runtime-config tunable, so treat 300\nas the default rather than a fixed contract, and drive retries from the\n`RateLimit-*` and `Retry-After` headers.\n\nThe body is the limiter's own literal, NOT the canonical envelope.\n",
        "headers": {
          "RateLimit-Limit": {
            "description": "Requests allowed in the current window",
            "schema": {
              "example": 300,
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "description": "Requests remaining (0 when limited)",
            "schema": {
              "example": 0,
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "description": "Seconds until the window resets",
            "schema": {
              "example": 42,
              "type": "integer"
            }
          },
          "Retry-After": {
            "description": "Seconds to wait before retrying",
            "schema": {
              "example": 60,
              "type": "integer"
            }
          }
        }
      },
      "Unauthorized": {
        "content": {
          "application/json": {
            "example": {
              "error": "Invalid or missing API key. Include x-api-key header or Authorization: Bearer header.",
              "success": false
            },
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        },
        "description": "Missing or invalid API key. Ensure you are including either the `x-api-key`\nheader or `Authorization: Bearer` header with a valid centralized platform API key.\n\n**Common causes:**\n- Missing authentication header entirely\n- API key has been revoked or rotated\n- The key has not been linked to a DSP seat yet (call `POST /openrtb/v2/onboard`\n  once with the same key)\n- Typo in the API key value\n",
        "headers": {
          "WWW-Authenticate": {
            "description": "Authentication method required",
            "schema": {
              "example": "ApiKey realm=\"Trillboards DSP API\"",
              "type": "string"
            }
          }
        }
      },
      "Unauthorized__programmatic": {
        "content": {
          "application/json": {
            "example": {
              "code": "NO_TOKEN",
              "message": "Token not provided"
            },
            "schema": {
              "$ref": "#/components/schemas/LegacyAuthError"
            }
          }
        },
        "description": "JWT bearer token missing, expired, or invalid.\n\nEmitted by the `verifyToken` middleware, which predates the canonical\n`{ error: { ... } }` envelope and returns a FLAT `{ message, code }` body.\n`code` is `NO_TOKEN` when no `Authorization` header was sent and\n`TOKEN_EXPIRED` for a well-formed but expired token. A structurally\ninvalid token returns **403** with `INVALID_TOKEN`, not 401 — do not\ntreat 403 on these routes as an authorization failure.\n"
      }
    },
    "schemas": {
      "AdvertisingIdentity": {
        "description": "Stateful resettable operating-system advertising identity. Send\n`available` only with the current OS ID. A non-available state clears\na previously observed value. HEM and UID2 do not belong here.\n",
        "properties": {
          "expires_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "description": "Present only when status is `available`.",
            "maxLength": 128,
            "minLength": 8,
            "nullable": true,
            "type": "string"
          },
          "id_type": {
            "enum": [
              "gaid",
              "aaid",
              "afai",
              "rida",
              "tifa",
              "idfa"
            ],
            "nullable": true,
            "type": "string"
          },
          "limit_ad_tracking": {
            "description": "Must be false for `available` and true for `limited`.",
            "nullable": true,
            "type": "boolean"
          },
          "observed_at": {
            "format": "date-time",
            "type": "string"
          },
          "source": {
            "enum": [
              "google_play_services",
              "amazon_fire_os",
              "roku_os",
              "samsung_tizen",
              "lg_webos",
              "apple_platform",
              "host_supplied"
            ],
            "type": "string"
          },
          "source_version": {
            "maxLength": 128,
            "nullable": true,
            "type": "string"
          },
          "status": {
            "enum": [
              "available",
              "limited",
              "consent_denied",
              "unavailable",
              "unsupported",
              "error"
            ],
            "type": "string"
          }
        },
        "required": [
          "status",
          "limit_ad_tracking",
          "source",
          "observed_at"
        ],
        "type": "object"
      },
      "ApiErrorEnvelope": {
        "description": "Canonical Trillboards error envelope, emitted by the global error\nhandler for every `/openrtb/**` route that raises a typed `ApiError`.\n\nThree DIFFERENT error shapes are reachable on this surface and they are\nnot interchangeable — branch on the HTTP status code, never on the body:\n\n| Shape | Emitted by | Body |\n|-------|-----------|------|\n| Canonical (this schema) | typed `ApiError` via the global handler | `{ error: { type, code, message, ... } }` |\n| DSP envelope | `/openrtb/v2` data routes and their rate limiter | `{ success: false, error, error_code, request_id }` |\n| Limiter literal | the tracking / programmatic-event rate limiters | `{ success: false, message, retryAfter }` |\n\nEvery response also carries an `X-Request-Id` header holding the same\nvalue as `error.request_id`; quote it in support requests.\n\nClients may opt into the pre-2026 flat body with `X-Error-Shape: legacy`,\nwhich returns `{ statusCode, message, code, param, request_id }` instead.\nNew integrations must not use it.\n",
        "properties": {
          "error": {
            "properties": {
              "code": {
                "description": "Stable machine-readable code. This — with the status code — is\nthe only field safe to branch on.\n",
                "example": "missing_required_field",
                "type": "string"
              },
              "details": {
                "description": "Present only when several field violations were found at once.",
                "items": {
                  "type": "object"
                },
                "type": "array"
              },
              "doc_url": {
                "description": "Permalink to the error reference, keyed on `code`.",
                "example": "https://api.trillboards.com/docs/errors#missing_required_field",
                "format": "uri",
                "type": "string"
              },
              "help": {
                "description": "Present only when the API can suggest a next step — for example\n`required_scope` on a scope failure, or `try_next` / `suggestions`\non a not-found.\n",
                "type": "object"
              },
              "message": {
                "description": "Human-readable explanation, for logs and developer consoles.\nDo NOT parse it; the wording is not part of the contract.\n",
                "example": "screenId is required.",
                "type": "string"
              },
              "param": {
                "description": "Offending field or header name, when one can be named.",
                "example": "screenId",
                "nullable": true,
                "type": "string"
              },
              "request_id": {
                "description": "Correlation id, `req_` plus 32 hex characters. Also returned in\nthe `X-Request-Id` response header.\n",
                "example": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                "type": "string"
              },
              "type": {
                "description": "Coarse error class, derived from the status code when the raiser\ndoes not set one explicitly.\n",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "permission_error",
                  "idempotency_error",
                  "rate_limit_error",
                  "api_error"
                ],
                "example": "invalid_request_error",
                "type": "string"
              }
            },
            "required": [
              "type",
              "code",
              "message",
              "doc_url"
            ],
            "type": "object"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "ApiKeyInfo": {
        "description": "Centralized platform-key linkage. Raw key appears only for legacy anonymous onboarding.",
        "properties": {
          "key": {
            "description": "Full API key — store securely, shown once only",
            "example": "tb_dsp_a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef12",
            "type": "string"
          },
          "key_id": {
            "format": "uuid",
            "type": "string"
          },
          "prefix": {
            "example": "tb_dsp_a1b2c",
            "type": "string"
          },
          "rate_limit_rpm": {
            "description": "Requests per minute (100 sandbox, 1000 production)",
            "example": 100,
            "type": "integer"
          },
          "reused_platform_key": {
            "type": "boolean"
          },
          "scopes": {
            "example": [
              "bid:write",
              "inventory:read",
              "deals:read",
              "stats:read",
              "campaigns:write",
              "reports:read",
              "proof:read"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "AudienceAttentionPrediction": {
        "description": "Backend-derived predictive attention signal + buyer-grade attentive-seconds. 1:1 with audience_metrics. Never null by contract; placeholder on row 1.",
        "properties": {
          "attentive_face_count": {
            "description": "attentive_face_count SMALLINT: distinct viewers meeting the >=1s threshold; the buyer-side CPMA denominator. NEVER NULL.",
            "example": 0,
            "format": "int32",
            "maximum": 1000,
            "minimum": 0,
            "type": "integer",
            "x-since": "2026-05-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "count"
          },
          "attentive_seconds_observed": {
            "description": "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).",
            "example": 500,
            "maximum": 999.999,
            "minimum": 0,
            "type": "number",
            "x-since": "2026-05-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "attentive_seconds_predicted_15s": {
            "description": "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).",
            "example": 500,
            "maximum": 999.999,
            "minimum": 0,
            "type": "number",
            "x-since": "2026-05-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_abs_error": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_attention_forecast": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_attention_realized": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_confidence": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_forecast_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_graded_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_horizon_minutes": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "count"
          },
          "next_state_method": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "text"
          },
          "next_state_persist_abs_error": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-06-16T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_sample_size": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-06-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "count"
          },
          "next_state_xscreen_abs_error": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-06-16T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_xscreen_method": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-06-16T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "text"
          },
          "next_state_xscreen_sample_size": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-06-16T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "count"
          },
          "next_state_xsv_abs_error": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-06-16T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
          },
          "next_state_xsv_sample_size": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-06-16T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "count"
          },
          "predicted_attention_next_30s": {
            "description": "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'.",
            "enum": [
              "high",
              "medium",
              "low",
              "none"
            ],
            "example": "high",
            "type": "string",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "enum"
          },
          "predicted_disengagement_imminent": {
            "description": "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.",
            "example": false,
            "type": "boolean",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "boolean"
          },
          "prediction_evidence": {
            "example": "sample",
            "maxLength": 280,
            "minLength": 1,
            "type": "string",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml",
            "x-units": "text"
          }
        },
        "required": [
          "predicted_attention_next_30s",
          "predicted_disengagement_imminent",
          "prediction_evidence",
          "attentive_seconds_observed",
          "attentive_face_count",
          "attentive_seconds_predicted_15s"
        ],
        "type": "object",
        "x-since": "2026-05-11T00:00:00.000Z",
        "x-sor-ref": "docs/schemas/audience-fein/audience_attention_prediction.yaml"
      },
      "AudienceSocialAttention": {
        "description": "Buyer-grade social amplification signal (5 typed columns). Cloud Vertex Gemini producer. 1 row per audience observation when cascade is observable.",
        "properties": {
          "amplification_evidence": {
            "example": "sample",
            "maxLength": 280,
            "minLength": 1,
            "type": "string",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml",
            "x-units": "text"
          },
          "cascade_depth": {
            "example": 0,
            "format": "int32",
            "maximum": 10,
            "minimum": 0,
            "type": "integer",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml",
            "x-units": "count"
          },
          "data_projection_version": {
            "example": 0,
            "format": "int64",
            "type": "integer",
            "x-since": "2026-09-30T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml",
            "x-units": "count"
          },
          "data_projection_xid": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-09-30T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml",
            "x-units": "text"
          },
          "primary_attention_anchor_face_index": {
            "description": "All 5 columns NOT NULL except primary_attention_anchor_face_index (only legit NULL: no anchor or solo scene).",
            "example": 0,
            "format": "int32",
            "minimum": 0,
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml",
            "x-units": "count"
          },
          "social_amplification_factor": {
            "description": "NUMERIC(4,3).",
            "example": 0.5,
            "maximum": 1,
            "minimum": 0,
            "type": "number",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml",
            "x-units": "ratio"
          },
          "viral_attention_score": {
            "description": "viral_attention_score is the headline number Viant/T-Vision license. Composite of social_amplification_factor × cascade_depth × emotional engagement. NUMERIC(4,3).",
            "example": 0.5,
            "maximum": 1,
            "minimum": 0,
            "type": "number",
            "x-since": "2026-05-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml",
            "x-units": "ratio"
          }
        },
        "required": [
          "social_amplification_factor",
          "cascade_depth",
          "viral_attention_score",
          "amplification_evidence",
          "data_projection_version",
          "data_projection_xid"
        ],
        "type": "object",
        "x-since": "2026-05-11T00:00:00.000Z",
        "x-sor-ref": "docs/schemas/audience-fein/audience_social_attention.yaml"
      },
      "BidRequest": {
        "example": {
          "dooh": {
            "venue": {
              "type": [
                "transit"
              ]
            }
          },
          "id": "br-001",
          "imp": [
            {
              "bidfloor": 2.5,
              "id": "imp-1",
              "video": {
                "h": 1080,
                "mimes": [
                  "video/mp4"
                ],
                "w": 1920
              }
            }
          ],
          "tmax": 500
        },
        "properties": {
          "app": {
            "description": "App placement context",
            "type": "object"
          },
          "device": {
            "properties": {
              "geo": {
                "properties": {
                  "country": {
                    "type": "string"
                  },
                  "lat": {
                    "type": "number"
                  },
                  "lon": {
                    "type": "number"
                  }
                },
                "type": "object"
              },
              "ip": {
                "type": "string"
              },
              "ua": {
                "type": "string"
              }
            },
            "type": "object"
          },
          "dooh": {
            "description": "DOOH-specific placement context (OpenRTB 2.6)",
            "properties": {
              "device": {
                "properties": {
                  "dth": {
                    "description": "Device type hint (4=tablet, 7=set-top-box)",
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "venue": {
                "properties": {
                  "type": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Unique bid request ID",
            "type": "string"
          },
          "imp": {
            "items": {
              "properties": {
                "bidfloor": {
                  "example": 2.5,
                  "type": "number"
                },
                "bidfloorcur": {
                  "default": "USD",
                  "type": "string"
                },
                "dt": {
                  "description": "Impression timestamp when supplied by the exchange.",
                  "format": "int64",
                  "type": "integer"
                },
                "id": {
                  "description": "Impression ID",
                  "type": "string"
                },
                "pmp": {
                  "properties": {
                    "deals": {
                      "items": {
                        "properties": {
                          "at": {
                            "type": "integer"
                          },
                          "bidfloor": {
                            "type": "number"
                          },
                          "bidfloorcur": {
                            "default": "USD",
                            "type": "string"
                          },
                          "id": {
                            "type": "string"
                          },
                          "wseat": {
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          }
                        },
                        "required": [
                          "id"
                        ],
                        "type": "object"
                      },
                      "maxItems": 10,
                      "type": "array"
                    },
                    "private_auction": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                },
                "qty": {
                  "description": "OpenRTB 2.6 quantity/multiplier contract used by DOOH supply.",
                  "properties": {
                    "multiplier": {
                      "type": "number"
                    },
                    "sourcetype": {
                      "type": "integer"
                    },
                    "vendor": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "video": {
                  "properties": {
                    "h": {
                      "example": 1080,
                      "type": "integer"
                    },
                    "mimes": {
                      "example": [
                        "video/mp4"
                      ],
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "protocols": {
                      "example": [
                        2,
                        5
                      ],
                      "items": {
                        "type": "integer"
                      },
                      "type": "array"
                    },
                    "w": {
                      "example": 1920,
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              },
              "required": [
                "id"
              ],
              "type": "object"
            },
            "maxItems": 10,
            "type": "array"
          },
          "site": {
            "description": "Web placement context",
            "type": "object"
          },
          "tmax": {
            "default": 500,
            "description": "Maximum response time in ms",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "imp"
        ],
        "type": "object"
      },
      "BidResponse": {
        "example": {
          "cur": "USD",
          "id": "br-001",
          "seatbid": [
            {
              "bid": [
                {
                  "adid": "ad-123",
                  "adm": "<VAST version=\"3.0\">...</VAST>",
                  "crid": "cr-456",
                  "h": 1080,
                  "id": "bid-1",
                  "impid": "imp-1",
                  "price": 3.25,
                  "w": 1920
                }
              ],
              "seat": "trillboards"
            }
          ]
        },
        "properties": {
          "cur": {
            "default": "USD",
            "type": "string"
          },
          "id": {
            "description": "Matches bid request ID",
            "type": "string"
          },
          "seatbid": {
            "items": {
              "properties": {
                "bid": {
                  "items": {
                    "properties": {
                      "adid": {
                        "type": "string"
                      },
                      "adm": {
                        "description": "VAST XML or ad markup",
                        "type": "string"
                      },
                      "burl": {
                        "description": "Billable-impression URL. Fire after the creative renders; this is the partner-exchange billable settlement event.",
                        "type": "string"
                      },
                      "crid": {
                        "description": "Creative ID",
                        "type": "string"
                      },
                      "h": {
                        "type": "integer"
                      },
                      "id": {
                        "type": "string"
                      },
                      "impid": {
                        "type": "string"
                      },
                      "lurl": {
                        "description": "Loss-notice URL. Fire the returned URL when the exchange does not select this bid.",
                        "type": "string"
                      },
                      "nurl": {
                        "description": "Win-notice URL. Fire after the exchange selects this bid; expand standard OpenRTB macros.",
                        "type": "string"
                      },
                      "price": {
                        "type": "number"
                      },
                      "w": {
                        "type": "integer"
                      }
                    },
                    "type": "object"
                  },
                  "type": "array"
                },
                "seat": {
                  "type": "string"
                }
              },
              "type": "object"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "BrandSafety": {
        "description": "Brand-safety rules for a screen — max age rating, blocked IAB\ncategories, and per-category allow flags for sensitive content.\n",
        "properties": {
          "allowed_sensitive": {
            "properties": {
              "adult": {
                "example": false,
                "type": "boolean"
              },
              "alcohol": {
                "example": false,
                "type": "boolean"
              },
              "cannabis": {
                "example": false,
                "type": "boolean"
              },
              "gambling": {
                "example": false,
                "type": "boolean"
              },
              "political": {
                "example": false,
                "type": "boolean"
              },
              "tobacco": {
                "example": false,
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "blocked_categories": {
            "example": [],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "content_filtering": {
            "example": true,
            "type": "boolean"
          },
          "max_age_rating": {
            "enum": [
              "G",
              "PG",
              "PG-13",
              "R",
              "NC-17"
            ],
            "example": "PG-13",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CompletedImpressions": {
        "description": "Completed-play observations from VAST complete and proof-of-play paths. Deduped by issued request/impression key for live revenue and daily earnings.",
        "properties": {
          "actual_cpm": {
            "description": "NUMERIC(18,6).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "actual_gross_revenue": {
            "description": "NUMERIC(18,6).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "ad_id": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "advertiser_domain": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-10-13T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "bid_request_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "billable_key": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "buyer_seat": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-10-13T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "completed_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "content_sha256": {
            "description": "content_sha256 is DECLARED to propagate from vast_requests via request_id but lands on ~1% of rows: PG vast_requests is empty (vastRequestsWriteBuffer defaultDisabled since 2026-06-26), so that lin...",
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-13T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "created_at": {
            "description": "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/...",
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "creative_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "currency": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "demand_partner_id": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "count"
          },
          "device_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "display_area_ratio": {
            "description": "NUMERIC(5,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "display_mode": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "duration_seconds": {
            "description": "NUMERIC(10,2).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "earnings_deal_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-09-22T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "earnings_deal_type": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-09-22T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "earnings_source": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-09-22T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "event_clock_skew_seconds": {
            "example": 1.5,
            "format": "float",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "event_timestamp_clamped": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "boolean"
          },
          "givt_passed": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "boolean"
          },
          "id": {
            "description": "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.",
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "count"
          },
          "impression_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "impression_multiplier": {
            "description": "NUMERIC(12,6).",
            "example": 1.5,
            "type": "number",
            "x-since": "2026-07-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "ingestion_path": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "metadata": {
            "description": "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...",
            "example": {},
            "type": "object",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "normalized_event_timestamp": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "play_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "pricing_source": {
            "description": "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...",
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "quantity_measured_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "quantity_source": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "quantity_source_type": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-07-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "count"
          },
          "quantity_vendor": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "raw_client_timestamp": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
          },
          "request_id": {
            "description": "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...",
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "screen_id": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "count"
          },
          "screen_mongo_id": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "serving_tenant_id": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-25T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "count"
          },
          "signature": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "source_provider": {
            "description": "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.",
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "variant_id": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "count"
          },
          "variant_name": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          },
          "zone_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml",
            "x-units": "text"
          }
        },
        "required": [
          "completed_at",
          "screen_mongo_id",
          "ad_id",
          "source_provider",
          "pricing_source",
          "currency",
          "ingestion_path",
          "metadata",
          "created_at",
          "impression_multiplier"
        ],
        "type": "object",
        "x-sor-ref": "docs/schemas/ad-lifecycle/completed_impressions.yaml"
      },
      "ContentClassification": {
        "description": "AI-generated content classification from Gemini moderation",
        "properties": {
          "age_rating": {
            "enum": [
              "G",
              "PG",
              "PG-13",
              "R",
              "NC-17"
            ],
            "example": "G",
            "type": "string"
          },
          "classification_confidence": {
            "example": 0.95,
            "maximum": 1,
            "minimum": 0,
            "type": "number"
          },
          "classification_reasons": {
            "example": [
              "recognized_brand_logo",
              "sports_content"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "contains_adult_content": {
            "example": false,
            "type": "boolean"
          },
          "contains_alcohol": {
            "example": false,
            "type": "boolean"
          },
          "contains_gambling": {
            "example": false,
            "type": "boolean"
          },
          "contains_political": {
            "example": false,
            "type": "boolean"
          },
          "contains_profanity": {
            "example": false,
            "type": "boolean"
          },
          "contains_religious": {
            "example": false,
            "type": "boolean"
          },
          "contains_tobacco": {
            "example": false,
            "type": "boolean"
          },
          "contains_violence": {
            "example": false,
            "type": "boolean"
          },
          "is_known_brand": {
            "example": true,
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "CpmBenchmark": {
        "description": "CPM percentile benchmark row. One row per venue type (optionally grouped\nby daypart or day depending on the `granularity` query parameter).\n",
        "properties": {
          "country": {
            "example": "US",
            "nullable": true,
            "type": "string"
          },
          "cpm": {
            "properties": {
              "avg": {
                "example": 7.42,
                "type": "number"
              },
              "median": {
                "example": 7.25,
                "type": "number"
              },
              "p25": {
                "example": 4.5,
                "type": "number"
              },
              "p75": {
                "example": 10.8,
                "type": "number"
              }
            },
            "type": "object"
          },
          "day": {
            "description": "Present when `granularity=daily`.",
            "format": "date",
            "nullable": true,
            "type": "string"
          },
          "daypart": {
            "description": "Present when `granularity=daypart`.",
            "example": "evening",
            "nullable": true,
            "type": "string"
          },
          "period": {
            "example": "30d",
            "type": "string"
          },
          "sample_size": {
            "example": 14520,
            "type": "integer"
          },
          "venue_type": {
            "example": "coffee_shop",
            "type": "string"
          }
        },
        "type": "object"
      },
      "DaypartCurve": {
        "description": "One daypart (morning/afternoon/evening/late_night) demand curve row.",
        "properties": {
          "avg_cpm": {
            "example": 9.28,
            "type": "number"
          },
          "bid_volume": {
            "example": 22040,
            "type": "integer"
          },
          "daypart": {
            "enum": [
              "morning",
              "afternoon",
              "evening",
              "late_night"
            ],
            "example": "evening",
            "type": "string"
          },
          "fill_rate": {
            "example": 0.91,
            "maximum": 1,
            "minimum": 0,
            "type": "number"
          }
        },
        "type": "object"
      },
      "Deal": {
        "description": "A programmatic deal (PMP, preferred, or guaranteed)",
        "properties": {
          "buyer": {
            "example": "acme_ads",
            "type": "string"
          },
          "daily_impression_cap": {
            "example": 50000,
            "nullable": true,
            "type": "integer"
          },
          "deal_id": {
            "example": "pmp-acme-retail-2026q2",
            "type": "string"
          },
          "deal_type": {
            "enum": [
              "pmp",
              "preferred",
              "programmatic_guaranteed"
            ],
            "example": "pmp",
            "type": "string"
          },
          "eligible_countries": {
            "example": [
              "US",
              "CA"
            ],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array"
          },
          "eligible_venue_types": {
            "example": [
              "retail",
              "restaurant"
            ],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array"
          },
          "end_date": {
            "example": "2026-06-30",
            "format": "date",
            "nullable": true,
            "type": "string"
          },
          "floor_cpm": {
            "description": "Minimum CPM in USD",
            "example": 2.5,
            "type": "number"
          },
          "impression_cap": {
            "description": "Total impression cap for the deal",
            "example": 500000,
            "nullable": true,
            "type": "integer"
          },
          "partner_key": {
            "example": "inbound_dsp_acme_ads",
            "type": "string"
          },
          "start_date": {
            "example": "2026-04-01",
            "format": "date",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "enum": [
              "active",
              "pending",
              "archived",
              "expired"
            ],
            "example": "active",
            "type": "string"
          }
        },
        "type": "object"
      },
      "DemandPartners": {
        "description": "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.",
        "properties": {
          "archived": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml",
            "x-units": "boolean"
          },
          "created_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml"
          },
          "demand_partner_id": {
            "description": "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 ...",
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml",
            "x-units": "text"
          },
          "display_name": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml",
            "x-units": "text"
          },
          "partner_key": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml",
            "x-units": "text"
          },
          "partner_type": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml",
            "x-units": "text"
          },
          "public_alias": {
            "description": "public_alias UNIQUE: this is the obfuscated external alias partner-API consumers see. partnerAlias.js will read from this table after Wave 2 cutover.",
            "example": "sample",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml",
            "x-units": "text"
          },
          "updated_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml"
          }
        },
        "required": [
          "partner_key",
          "public_alias",
          "display_name",
          "partner_type",
          "archived",
          "created_at",
          "updated_at"
        ],
        "type": "object",
        "x-since": "2026-05-01T00:00:00.000Z",
        "x-sor-ref": "docs/schemas/partnerships/demand_partners.yaml"
      },
      "DeviceHeartbeatBody": {
        "additionalProperties": true,
        "anyOf": [
          {
            "required": [
              "fingerprint"
            ]
          },
          {
            "required": [
              "screenId"
            ]
          }
        ],
        "description": "Heartbeat shape emitted by agent-core and agent-core-lite. Supply\n`fingerprint`, `screenId`, or both. Unknown telemetry fields remain\nforward-compatible; targeting-identity fields are strictly validated.\n",
        "properties": {
          "advertising_id": {
            "deprecated": true,
            "description": "Legacy MAID field; prefer `advertising_identity`.",
            "nullable": true,
            "type": "string"
          },
          "advertising_id_type": {
            "deprecated": true,
            "enum": [
              "gaid",
              "aaid",
              "afai",
              "rida",
              "tifa",
              "idfa"
            ],
            "nullable": true,
            "type": "string"
          },
          "advertising_identity": {
            "$ref": "#/components/schemas/AdvertisingIdentity"
          },
          "capabilities": {
            "additionalProperties": true,
            "type": "object"
          },
          "extended_identities": {
            "items": {
              "$ref": "#/components/schemas/ExtendedIdentity"
            },
            "maxItems": 16,
            "type": "array"
          },
          "fingerprint": {
            "description": "Stable registered device external ID or fingerprint.",
            "minLength": 1,
            "type": "string"
          },
          "limit_ad_tracking": {
            "deprecated": true,
            "nullable": true,
            "type": "boolean"
          },
          "metadata": {
            "additionalProperties": true,
            "type": "object"
          },
          "schema_version": {
            "maximum": 255,
            "minimum": 1,
            "type": "integer"
          },
          "screenId": {
            "description": "Bound screen ID when already known by the device.",
            "minLength": 1,
            "type": "string"
          },
          "status": {
            "example": "online",
            "type": "string"
          }
        },
        "type": "object"
      },
      "DspErrorEnvelope": {
        "description": "Error shape used by the `/openrtb/v2` DSP data routes and by the DSP\nrate limiters. Distinguished from the canonical envelope by the\ntop-level `success: false` and by `error` being a STRING rather than an\nobject.\n",
        "properties": {
          "error": {
            "description": "Human-readable message. Do not parse.",
            "example": "cursor is not valid base64-encoded JSON",
            "type": "string"
          },
          "error_code": {
            "description": "Stable machine-readable code.",
            "example": "invalid_cursor",
            "type": "string"
          },
          "granted_scopes": {
            "description": "Scopes the key actually holds. Present on a `missing_scope` 403 only.\n",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "param": {
            "description": "Offending query parameter, when the route names one.",
            "example": "cursor",
            "type": "string"
          },
          "request_id": {
            "description": "Correlation id, mirrored in the `X-Request-Id` header.",
            "example": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
            "nullable": true,
            "type": "string"
          },
          "required_scope": {
            "description": "Present on scope failures only.",
            "example": "inventory:read",
            "type": "string"
          },
          "retry_after_seconds": {
            "description": "Present on 429 responses only.",
            "example": 60,
            "type": "integer"
          },
          "success": {
            "description": "Always `false`.",
            "example": false,
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "error",
          "error_code"
        ],
        "type": "object"
      },
      "DspRegistration": {
        "description": "DSP registration record returned after onboarding",
        "properties": {
          "company_name": {
            "example": "Acme Advertising",
            "type": "string"
          },
          "created_at": {
            "example": "2026-04-05T14:30:00Z",
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
            "format": "uuid",
            "type": "string"
          },
          "openrtb_version": {
            "example": "2.6",
            "type": "string"
          },
          "seat_id": {
            "example": "acme_ads",
            "type": "string"
          },
          "status": {
            "enum": [
              "sandbox",
              "approved",
              "suspended",
              "revoked"
            ],
            "example": "sandbox",
            "type": "string"
          }
        },
        "type": "object"
      },
      "EarnerScreens": {
        "description": "User-owned ad screens. Each row is a logical screen that can display ads. Links user to device. PG primary with Mongo legacy mirror.",
        "properties": {
          "ad_interval_seconds": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "ads_disabled_reasons": {
            "example": [],
            "items": {
              "type": "string"
            },
            "type": "array",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "advertisement_sizing": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "archetype_photo_key": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-08-18T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "archetype_photo_synced_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-08-18T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "archetype_photo_url": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-08-18T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "auction_cohort": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "auction_enabled_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "auction_mode": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "auction_type": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "avg_dwell_time_seconds": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "back_online_alert_sent_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-04T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "billing_mode": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "block_group_geoid": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-04T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "blocked_advertisers": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "blocked_iab_categories": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "brand_substitution_consent_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "brand_substitution_consent_by": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "brand_substitution_mode": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "branding_overlay_enabled": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "branding_overlay_hide_on_ads": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "branding_overlay_logo_transformed_url": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "branding_overlay_logo_url": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "branding_overlay_opacity": {
            "description": "NUMERIC(5,4).",
            "example": 1.5,
            "type": "number",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "branding_overlay_padding_ratio": {
            "description": "NUMERIC(5,4).",
            "example": 1.5,
            "type": "number",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "branding_overlay_placement": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-07-16T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "branding_overlay_position": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "branding_overlay_size_ratio": {
            "description": "NUMERIC(5,4).",
            "example": 1.5,
            "type": "number",
            "x-since": "2026-04-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "cms_payment_status": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "commercial_value_score": {
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "content_filtering_enabled": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "deleted_at": {
            "description": "deleted_at IS NULL required — soft-delete nulls earnerscreen_device_id for partial unique index",
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "delivery_mode": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-04-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "device_platform_override": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-15T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "diagonal_inches": {
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "display_mode": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "distributor_id": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "dma_code": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-04T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "dma_name": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-04T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "dynamic_floor_pricing": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "enrichment_attempts": {
            "example": 0,
            "format": "int32",
            "type": "integer",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "enrichment_failed_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-05T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "environment_type": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "floor_price_cpm": {
            "description": "NUMERIC(12,6).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "foot_traffic_multiplier": {
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "gam_ad_unit_code": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "gam_ad_unit_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "gam_ad_unit_path": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "gam_sync_status": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "gam_synced_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "geocode_source": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "geocoded_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-22T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "geocoding_raw_response": {
            "example": {},
            "nullable": true,
            "type": "object",
            "x-since": "2026-05-22T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "hb_allocation": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "hb_enabled": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "hb_force_request": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "hb_last_sync_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "hb_min_interval_seconds": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "hb_provider": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "hb_request_mode": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "hb_timeout_ms": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "hb_vast_tag_url": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "height_inches": {
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "hive_stack_screen_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "hive_stack_site_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "hive_stack_sync_status": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "hive_stack_synced_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "hive_stack_unit_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "hive_stack_unit_uuid": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "is_demo_data": {
            "example": false,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "is_standby": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-04-08T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "l_bar_ads_enabled": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "l_bar_position": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-06-22T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "last_enrichment_attempt_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "last_offline_transition_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-08T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "last_programmatic_sync_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "latitude": {
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "longitude": {
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "loop_plan_id": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "media_capability": {
            "enum": [
              "image_video",
              "image_only",
              "video_only"
            ],
            "example": "image_video",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "enum"
          },
          "mediation_priority": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "mobile_distance_threshold_m": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "mobile_update_interval_s": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "mobile_vehicle_type": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "neighborhood": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-05T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "ntp_sync_required": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-04-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "offline_alert_sent_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-04T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "offline_push_alert_sent_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-09-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "operating_hours": {
            "example": {},
            "nullable": true,
            "type": "object",
            "x-since": "2026-05-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "partner_mongo_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "peak_hours": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "photo_flag_notes": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "photo_flag_reason": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "photo_flagged": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "photo_flagged_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "photo_flagged_by_mongo_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "photo_notification_count": {
            "example": 0,
            "format": "int32",
            "type": "integer",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "photo_notification_sent_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "photo_quality_sent_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-26T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "photo_review_status": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "photos": {
            "example": {},
            "nullable": true,
            "type": "object",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "physical_size_set_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-02T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "physical_size_set_by": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-02T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "place_name": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-07-05T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "placement_type": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "places_rating": {
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "places_review_count": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "pref_allow_religious": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "pref_allow_tobacco": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "pref_allowed_categories": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "pref_allowed_keywords": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "pref_allowed_subjects": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "pref_auto_approve_known_brands": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "pref_blocked_categories": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "pref_blocked_keywords": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "pref_blocked_subjects": {
            "example": [],
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array",
            "x-since": "2026-04-10T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "pref_level": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "pref_max_age_rating": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "preferred_deal_ids": {
            "example": [],
            "items": {
              "type": "string"
            },
            "type": "array",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "prog_allocation_pct": {
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "prog_currency": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "programmatic_enabled": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "region": {
            "example": "sample",
            "type": "string",
            "x-since": "2026-05-02T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "registration_source": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-08T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "screen_placement": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "serving_tenant_id": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "slot_duration_seconds": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-12T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "sound_enabled": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "ssp_enabled": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "state_iso_3166_2": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-22T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "sync_enabled": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-since": "2026-04-06T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "sync_group_name": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-06T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "sync_is_master": {
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-since": "2026-04-06T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "boolean"
          },
          "sync_last_sync_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-06T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "sync_master_screen_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-06T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "taggify_screen_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "taxonomy_migrated_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "taxonomy_spec_version": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_category": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_child": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_child_id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_city": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_classification_source": {
            "description": "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...",
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-14T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_grandchild": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_parent": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_state": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-11T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "venue_subcategory": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "text"
          },
          "waterfall_optimized_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "waterfall_partners": {
            "description": "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...",
            "example": [],
            "items": {
              "type": "string"
            },
            "type": "array",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          },
          "waterfall_total_timeout_ms": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-21T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml",
            "x-units": "count"
          },
          "width_inches": {
            "description": "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.",
            "example": 1.5,
            "format": "double",
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
          }
        },
        "required": [
          "is_standby",
          "delivery_mode",
          "ntp_sync_required",
          "waterfall_partners",
          "programmatic_enabled",
          "prog_currency",
          "preferred_deal_ids",
          "branding_overlay_enabled",
          "branding_overlay_logo_url",
          "branding_overlay_logo_transformed_url",
          "branding_overlay_position",
          "branding_overlay_size_ratio",
          "branding_overlay_padding_ratio",
          "branding_overlay_opacity",
          "branding_overlay_hide_on_ads",
          "region",
          "photo_flagged",
          "photo_notification_count",
          "branding_overlay_placement",
          "is_demo_data",
          "billing_mode",
          "ads_disabled_reasons",
          "cms_payment_status",
          "brand_substitution_mode",
          "enrichment_attempts"
        ],
        "type": "object",
        "x-sor-ref": "docs/schemas/earners-supply/earner_screens.yaml"
      },
      "ErrorCode": {
        "description": "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.",
        "enum": [
          "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"
        ],
        "type": "string"
      },
      "ErrorEnvelope": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/TrillboardsError"
          },
          {
            "$ref": "#/components/schemas/HybridError"
          },
          {
            "$ref": "#/components/schemas/LegacyCodedError"
          },
          {
            "$ref": "#/components/schemas/LegacyError"
          }
        ],
        "description": "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.",
        "type": "object",
        "x-envelope-set": "error"
      },
      "ErrorResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/LegacyError"
          }
        ],
        "description": "Standard error response format used across all DSP API endpoints.\nAll error responses include `success: false` and a human-readable\n`error` message describing what went wrong. Do not parse `error`\nprogrammatically — use the HTTP status code for control flow, or move to\n`TrillboardsError`, whose `code` is a stable machine-readable enum.\n\nCOLLAPSED onto the shared `LegacyError` definition so there is exactly\none declaration of this shape across the specs.\n",
        "x-superseded-by": "TrillboardsError"
      },
      "ErrorType": {
        "description": "Stripe-style error taxonomy. Derived from the distinct `type` values in utils/errorCatalog.js (6 values).",
        "enum": [
          "api_error",
          "authentication_error",
          "idempotency_error",
          "invalid_request_error",
          "permission_error",
          "rate_limit_error"
        ],
        "type": "string"
      },
      "ExtendedIdentity": {
        "description": "Exact buyer-matchable audience identity. `hem_sha256` is a\nprecomputed 64-character SHA-256 hex digest; the server lowercases the\nrepresentation once and never hashes it again. `uid2_token` is opaque\nand case-sensitive, and contains no whitespace. Use `unavailable`\nwithout `id` to clear a value.\n",
        "properties": {
          "atype": {
            "description": "OpenRTB person-based identifier type.",
            "enum": [
              3
            ],
            "type": "integer"
          },
          "expires_at": {
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "id": {
            "maxLength": 4096,
            "minLength": 1,
            "nullable": true,
            "pattern": "^\\S+$",
            "type": "string"
          },
          "id_type": {
            "enum": [
              "hem_sha256",
              "uid2_token"
            ],
            "type": "string"
          },
          "observed_at": {
            "format": "date-time",
            "type": "string"
          },
          "source": {
            "description": "Canonical identity-provider domain.",
            "example": "uidapi.com",
            "maxLength": 253,
            "minLength": 1,
            "pattern": "^(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)+[A-Za-z]{2,63}$",
            "type": "string"
          },
          "source_version": {
            "maxLength": 128,
            "nullable": true,
            "type": "string"
          },
          "status": {
            "enum": [
              "available",
              "unavailable"
            ],
            "type": "string"
          }
        },
        "required": [
          "status",
          "id_type",
          "source",
          "atype",
          "observed_at"
        ],
        "type": "object"
      },
      "FEINSignals": {
        "description": "Real-time audience signals from FEIN (Face/Edge Intelligence Network). Updated every 10 seconds from on-device edge AI.",
        "properties": {
          "ad_receptivity": {
            "description": "Predicted ad receptivity score",
            "example": 0.68,
            "maximum": 1,
            "minimum": 0,
            "nullable": true,
            "type": "number"
          },
          "attention_score": {
            "description": "Average gaze attention (0=none, 1=full)",
            "example": 0.78,
            "maximum": 1,
            "minimum": 0,
            "type": "number"
          },
          "avg_attention_score_7d": {
            "example": 0.72,
            "maximum": 1,
            "minimum": 0,
            "nullable": true,
            "type": "number"
          },
          "crowd_density": {
            "description": "Estimated venue occupancy",
            "example": 45,
            "nullable": true,
            "type": "integer"
          },
          "data_quality": {
            "description": "live=real-time FEIN, historical=only VAS, none=no audience data",
            "enum": [
              "live",
              "historical",
              "none"
            ],
            "example": "live",
            "type": "string"
          },
          "dominant_emotion": {
            "description": "Most common detected emotion",
            "enum": [
              "happy",
              "sad",
              "angry",
              "surprised",
              "neutral",
              "fear",
              "disgust"
            ],
            "example": "happy",
            "nullable": true,
            "type": "string"
          },
          "dwell_time_ms": {
            "description": "Average audience dwell time in milliseconds",
            "example": 12000,
            "type": "integer"
          },
          "income_level": {
            "enum": [
              "low",
              "medium",
              "high",
              "premium"
            ],
            "example": "medium",
            "nullable": true,
            "type": "string"
          },
          "last_updated": {
            "example": "2026-04-05T14:30:00Z",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "live_face_count": {
            "description": "People currently facing the screen",
            "example": 12,
            "type": "integer"
          },
          "purchase_intent": {
            "description": "Speech-derived purchase intent level",
            "example": "moderate",
            "nullable": true,
            "type": "string"
          },
          "vas_7d": {
            "description": "Verified Attention Seconds — 7-day weighted rolling average",
            "example": 6.5,
            "nullable": true,
            "type": "number"
          },
          "vas_measurements_7d": {
            "description": "Number of VAS measurements in last 7 days",
            "example": 150,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "FeinAuthError": {
        "description": "Flat body emitted by the FEIN `X-API-Key` middleware.",
        "properties": {
          "error": {
            "example": "Invalid or missing FEIN API key",
            "type": "string"
          },
          "retry_after_seconds": {
            "description": "Present on the FEIN 429 only.",
            "example": 60,
            "type": "integer"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "FunnelStats": {
        "description": "VAST event funnel totals plus computed rates. Maps rollup columns to\nDOOH funnel semantics: `response_count → bids_received`,\n`filled_count → renders_started`, `completed_count → plays_completed`.\n",
        "properties": {
          "bid_rate": {
            "description": "bids_received / requests",
            "example": 0.921,
            "type": "number"
          },
          "bids_received": {
            "example": 26210,
            "type": "integer"
          },
          "errors": {
            "example": 420,
            "type": "integer"
          },
          "fill_rate": {
            "description": "renders_started / requests",
            "example": 0.875,
            "type": "number"
          },
          "no_bids": {
            "description": "Approximate no-bid count (`requests - bids - errors`).",
            "example": 1820,
            "type": "integer"
          },
          "plays_completed": {
            "example": 22140,
            "type": "integer"
          },
          "renders_started": {
            "example": 24890,
            "type": "integer"
          },
          "requests": {
            "example": 28450,
            "type": "integer"
          },
          "start_to_complete": {
            "description": "plays_completed / renders_started",
            "example": 0.889,
            "type": "number"
          }
        },
        "type": "object"
      },
      "HybridError": {
        "description": "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.",
        "properties": {
          "error": {
            "properties": {
              "code": {
                "description": "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`.",
                "example": "not_found",
                "type": "string",
                "x-error-code-catalog": "#/components/schemas/ErrorCode"
              },
              "param": {
                "nullable": true,
                "type": "string"
              },
              "type": {
                "$ref": "#/components/schemas/ErrorType"
              }
            },
            "type": "object"
          },
          "message": {
            "type": "string"
          },
          "success": {
            "example": false,
            "type": "boolean"
          }
        },
        "type": "object",
        "x-superseded-by": "TrillboardsError"
      },
      "LegacyAuthError": {
        "description": "Flat body emitted by the `verifyToken` JWT middleware on the\nbearer-protected analytics and admin routes. Predates the canonical\nenvelope; `code` is the field to branch on.\n",
        "properties": {
          "code": {
            "description": "`NO_TOKEN` and `TOKEN_EXPIRED` come back as 401; `INVALID_TOKEN`\ncomes back as **403**.\n",
            "enum": [
              "NO_TOKEN",
              "TOKEN_EXPIRED",
              "INVALID_TOKEN"
            ],
            "example": "NO_TOKEN",
            "type": "string"
          },
          "message": {
            "example": "Token not provided",
            "type": "string"
          }
        },
        "required": [
          "message",
          "code"
        ],
        "type": "object"
      },
      "LegacyCodedError": {
        "description": "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.",
        "properties": {
          "code": {
            "description": "Legacy SCREAMING_SNAKE code, e.g. NO_TOKEN, TOKEN_EXPIRED, NO_API_KEY.",
            "example": "NO_TOKEN",
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "success": {
            "example": false,
            "type": "boolean"
          }
        },
        "required": [
          "message"
        ],
        "type": "object",
        "x-superseded-by": "TrillboardsError"
      },
      "LegacyError": {
        "description": "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`.",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "success": {
            "example": false,
            "type": "boolean"
          }
        },
        "type": "object",
        "x-superseded-by": "TrillboardsError"
      },
      "ModerationAnalysis": {
        "description": "Detailed AI analysis of creative content",
        "properties": {
          "keywords": {
            "example": [
              "Nike",
              "running",
              "sport",
              "fitness"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "mood": {
            "example": "energetic",
            "type": "string"
          },
          "quality": {
            "example": "professional",
            "type": "string"
          },
          "setting": {
            "example": "urban street scene",
            "type": "string"
          },
          "subjects": {
            "example": [
              "athletic shoes",
              "running"
            ],
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "OpenrtbRevenueScreenHourly": {
        "description": "Revenue per screen per hour. Aggregates gross revenue from completed impressions. Refreshed from CH via clickhouseRollupSource. PG only.",
        "properties": {
          "actual_gross_revenue": {
            "example": 1.5,
            "type": "number",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml"
          },
          "estimated_gross_revenue": {
            "example": 1.5,
            "type": "number",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml"
          },
          "estimated_impressions": {
            "example": 0,
            "format": "int64",
            "type": "integer",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml",
            "x-units": "count"
          },
          "gross_revenue": {
            "example": 1.5,
            "type": "number",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml"
          },
          "hour_bucket": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml"
          },
          "impressions": {
            "example": 0,
            "format": "int64",
            "type": "integer",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml",
            "x-units": "count"
          },
          "priced_impressions": {
            "example": 0,
            "format": "int64",
            "type": "integer",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml",
            "x-units": "count"
          },
          "screen_mongo_id": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml",
            "x-units": "text"
          },
          "updated_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml"
          }
        },
        "required": [
          "hour_bucket",
          "screen_mongo_id",
          "impressions",
          "priced_impressions",
          "estimated_impressions",
          "actual_gross_revenue",
          "estimated_gross_revenue",
          "gross_revenue",
          "updated_at"
        ],
        "type": "object",
        "x-sor-ref": "docs/schemas/rollups-reporting/openrtb_revenue_screen_hourly.yaml"
      },
      "Pagination": {
        "properties": {
          "count": {
            "example": 12,
            "type": "integer"
          },
          "limit": {
            "example": 50,
            "type": "integer"
          },
          "offset": {
            "example": 0,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "PartnerReconciliationReports": {
        "description": "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.",
        "properties": {
          "adjustment_id": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          },
          "applied_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "applied_report_hash": {
            "description": "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...",
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "text"
          },
          "created_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "demand_partner_id": {
            "description": "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.",
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          },
          "id": {
            "description": "VidVerto synced daily via vidvertoRevenueSync before programmatic earnings aggregation.",
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          },
          "imported_by": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "text"
          },
          "impression_variance": {
            "description": "NUMERIC(8,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "our_ecpm": {
            "description": "NUMERIC(10,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "our_impressions": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          },
          "our_requests": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-13T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          },
          "our_revenue": {
            "description": "NUMERIC(14,6).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "provider_name": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "text"
          },
          "raw_data": {
            "description": "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...",
            "example": {},
            "nullable": true,
            "type": "object",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "report_date": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "report_type": {
            "enum": [
              "partner_api",
              "manual_upload",
              "email_extract",
              "dashboard_snapshot"
            ],
            "example": "partner_api",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "enum"
          },
          "request_variance": {
            "description": "NUMERIC(10,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-04-13T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "resolution_notes": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "text"
          },
          "revenue_variance": {
            "description": "NUMERIC(8,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "reviewed_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "reviewed_by": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "text"
          },
          "status": {
            "enum": [
              "pending",
              "matched",
              "flagged",
              "disputed",
              "resolved",
              "accepted"
            ],
            "example": "pending",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "enum"
          },
          "their_ecpm": {
            "description": "NUMERIC(10,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "their_fill_rate": {
            "description": "NUMERIC(6,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "their_impressions": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          },
          "their_requests": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-04-13T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          },
          "their_revenue": {
            "description": "NUMERIC(14,6).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "updated_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
          },
          "variant_id": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml",
            "x-units": "count"
          }
        },
        "required": [
          "provider_name",
          "report_date",
          "report_type",
          "status",
          "created_at",
          "updated_at"
        ],
        "type": "object",
        "x-sor-ref": "docs/schemas/ad-lifecycle/partner_reconciliation_reports.yaml"
      },
      "PartnerVariants": {
        "description": "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.",
        "properties": {
          "archived": {
            "example": false,
            "type": "boolean",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "boolean"
          },
          "created_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml"
          },
          "daily_budget_cap": {
            "description": "NUMERIC(10,2).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml"
          },
          "daily_impression_cap": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "daily_request_cap": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "demand_partner_id": {
            "description": "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...",
            "example": 0,
            "format": "int32",
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "display_name": {
            "description": "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).",
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "text"
          },
          "public_alias": {
            "description": "public_alias is the obfuscation alias for partner-API responses (matches partnerAlias.encodeAlias pattern: 'var_' || sha256(variant_name)[0..5]).",
            "example": "sample",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "text"
          },
          "request_pressure_base_interval_seconds": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "request_pressure_completion_rate_floor": {
            "description": "NUMERIC(12,8).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml"
          },
          "request_pressure_confidence_level": {
            "description": "NUMERIC(6,3).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml"
          },
          "request_pressure_defer_seconds": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "request_pressure_enabled": {
            "description": "request_pressure_enabled=false fails open for pressure shaping only. It must not be treated as demand-source enablement.",
            "example": false,
            "nullable": true,
            "type": "boolean",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "boolean"
          },
          "request_pressure_max_multiplier": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "request_pressure_min_pair_requests": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "request_pressure_min_source_requests": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "request_pressure_probe_interval_seconds": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "request_pressure_probe_modulo": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "count"
          },
          "request_pressure_probe_target_per_minute": {
            "description": "NUMERIC(10,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml"
          },
          "updated_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml"
          },
          "variant_id": {
            "description": "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.",
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "text"
          },
          "variant_name": {
            "description": "variant_name TEXT is the wire-format alternate-key (kept forever) — internal logs and partner integrations rely on it. Never propose dropping variant_name.",
            "example": "sample",
            "type": "string",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml",
            "x-units": "text"
          }
        },
        "required": [
          "demand_partner_id",
          "variant_name",
          "public_alias",
          "archived",
          "created_at",
          "updated_at"
        ],
        "type": "object",
        "x-since": "2026-05-01T00:00:00.000Z",
        "x-sor-ref": "docs/schemas/partnerships/partner_variants.yaml"
      },
      "PartnerZoneCpmDaily": {
        "description": "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.",
        "properties": {
          "created_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
          },
          "demand_partner_id": {
            "description": "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.",
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "count"
          },
          "ecpm": {
            "description": "NUMERIC(10,4).",
            "example": 1.5,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
          },
          "fetched_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
          },
          "fill_rate": {
            "description": "NUMERIC(6,4).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
          },
          "gross_revenue": {
            "description": "NUMERIC(14,6).",
            "example": 1.5,
            "nullable": true,
            "type": "number",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
          },
          "id": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "text"
          },
          "impressions": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "count"
          },
          "partner_key": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "text"
          },
          "period_date": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
          },
          "requests": {
            "example": 0,
            "format": "int64",
            "nullable": true,
            "type": "integer",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "count"
          },
          "source": {
            "description": "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.",
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "text"
          },
          "updated_at": {
            "example": "2026-05-12T00:00:00.000Z",
            "format": "date-time",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
          },
          "variant_id": {
            "example": 0,
            "format": "int32",
            "nullable": true,
            "type": "integer",
            "x-since": "2026-05-01T00:00:00.000Z",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "count"
          },
          "zone_id": {
            "example": "sample",
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "text"
          },
          "zone_label": {
            "example": "sample",
            "nullable": true,
            "type": "string",
            "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml",
            "x-units": "text"
          }
        },
        "required": [
          "partner_key",
          "zone_id",
          "period_date",
          "ecpm",
          "source",
          "fetched_at",
          "created_at",
          "updated_at"
        ],
        "type": "object",
        "x-sor-ref": "docs/schemas/ad-lifecycle/partner_zone_cpm_daily.yaml"
      },
      "PhysicalSize": {
        "description": "Physical display dimensions in inches with a confidence tier indicating\nhow the measurements were captured.\n",
        "properties": {
          "confidence": {
            "description": "How the size was determined:\n- `measured` — verified by admin or on-site technician\n- `estimated` — derived from device dimensions/model lookup\n- `default` — fallback value (no device data available)\n",
            "enum": [
              "measured",
              "estimated",
              "default"
            ],
            "example": "estimated",
            "type": "string"
          },
          "diagonal_inches": {
            "example": 43,
            "nullable": true,
            "type": "number"
          },
          "height_inches": {
            "example": 21.1,
            "nullable": true,
            "type": "number"
          },
          "unit": {
            "description": "Measurement unit for width/height/diagonal — always \"inches\" (AMP \"Height/Width Parameter\").",
            "example": "inches",
            "type": "string"
          },
          "width_inches": {
            "example": 37.5,
            "nullable": true,
            "type": "number"
          }
        },
        "type": "object"
      },
      "ProofOfPlayRecord": {
        "description": "A single verified ad play record from `completed_impressions`.\n",
        "properties": {
          "creative_id": {
            "example": "creative_summer_sale",
            "type": "string"
          },
          "display_area_ratio": {
            "description": "Display area occupied by the ad (1.0 for fullscreen).",
            "example": 1,
            "type": "number"
          },
          "display_mode": {
            "example": "fullscreen",
            "type": "string"
          },
          "duration_seconds": {
            "example": 15,
            "nullable": true,
            "type": "number"
          },
          "givt_passed": {
            "description": "Whether the play passed GIVT (General Invalid Traffic) checks.",
            "example": true,
            "type": "boolean"
          },
          "play_id": {
            "example": "play_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
            "type": "string"
          },
          "played_at": {
            "example": "2026-05-01T14:22:31.000Z",
            "format": "date-time",
            "type": "string"
          },
          "screen_id": {
            "example": "65a1b2c3d4e5f6a7b8c9d0e1",
            "type": "string"
          },
          "signature": {
            "description": "Ed25519 signature when `verification_tier=signed`.",
            "example": "ed25519:AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=",
            "nullable": true,
            "type": "string"
          },
          "verification_tier": {
            "description": "Maps `ingestion_path` to a DSP-facing tier name:\n- `signed` — Ed25519 signed proof-of-play from the screen\n- `event_confirmed` — VAST `complete` event fired by the IMA SDK\n- `impression_callback` — Impression pixel fired by the screen\n",
            "enum": [
              "signed",
              "event_confirmed",
              "impression_callback"
            ],
            "example": "signed",
            "type": "string"
          }
        },
        "type": "object"
      },
      "RateLimitError": {
        "description": "Body returned by the `openrtbTracking` and `partnerProgrammaticEvent`\nrate limiters. It is the limiter's own literal and shares no fields with\neither the canonical or the DSP envelope — note `message`, not `error`,\nand `retryAfter` in camelCase.\n",
        "properties": {
          "message": {
            "example": "OpenRTB tracking rate limit exceeded.",
            "type": "string"
          },
          "retryAfter": {
            "description": "Seconds to wait. Mirrors the `Retry-After` header.",
            "example": 60,
            "type": "integer"
          },
          "success": {
            "example": false,
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "message"
        ],
        "type": "object"
      },
      "ScreenAudienceProfile": {
        "description": "Audience profile for a single screen. Combines live Redis signals,\nVAS rolling averages, and venue intelligence. All sections are nullable\nfor graceful degradation — a screen with no live data still returns\n`vas` and `venue` where available.\n",
        "properties": {
          "behavior": {
            "nullable": true,
            "properties": {
              "ad_receptivity": {
                "example": 0.68,
                "maximum": 1,
                "minimum": 0,
                "nullable": true,
                "type": "number"
              },
              "brand_mentions": {
                "example": [
                  "Starbucks"
                ],
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "dominant_emotion": {
                "example": "happy",
                "nullable": true,
                "type": "string"
              },
              "emotional_engagement": {
                "example": 0.72,
                "maximum": 1,
                "minimum": 0,
                "nullable": true,
                "type": "number"
              },
              "purchase_intent": {
                "example": "moderate",
                "nullable": true,
                "type": "string"
              },
              "screen_engagement": {
                "example": 0.81,
                "nullable": true,
                "type": "number"
              },
              "shopping_contexts": {
                "example": [
                  "coffee",
                  "breakfast"
                ],
                "items": {
                  "type": "string"
                },
                "type": "array"
              }
            },
            "type": "object"
          },
          "data_quality": {
            "enum": [
              "live",
              "historical",
              "none"
            ],
            "example": "live",
            "type": "string"
          },
          "demographics": {
            "nullable": true,
            "properties": {
              "avg_attention_score": {
                "example": 0.76,
                "maximum": 1,
                "minimum": 0,
                "type": "number"
              },
              "avg_dwell_time_sec": {
                "example": 12,
                "type": "number"
              },
              "avg_face_count": {
                "example": 4.2,
                "type": "number"
              },
              "crowd_density": {
                "example": 12,
                "nullable": true,
                "type": "integer"
              },
              "group_composition": {
                "description": "Dominant group type (solo, couples, families, friends, work, mixed).",
                "example": "couples",
                "nullable": true,
                "type": "string"
              },
              "income_level": {
                "enum": [
                  "low",
                  "medium",
                  "high",
                  "premium",
                  null
                ],
                "example": "medium",
                "nullable": true,
                "type": "string"
              }
            },
            "type": "object"
          },
          "patterns": {
            "description": "Reserved for hourly audience patterns (ClickHouse-backed). Returns\n`null` until the follow-up release ships.\n",
            "nullable": true,
            "type": "object"
          },
          "screen_id": {
            "example": "65a1b2c3d4e5f6a7b8c9d0e1",
            "type": "string"
          },
          "signals_age_ms": {
            "description": "Milliseconds since Redis last refreshed live signals. `null`\nwhen there are no live signals.\n",
            "example": 8200,
            "nullable": true,
            "type": "integer"
          },
          "vas": {
            "description": "Verified Attention Seconds scoring",
            "nullable": true,
            "properties": {
              "cpm_multiplier": {
                "example": 1.4,
                "type": "number"
              },
              "score": {
                "example": 6.52,
                "type": "number"
              },
              "tier": {
                "enum": [
                  "unverified",
                  "baseline",
                  "standard",
                  "premium",
                  "elite"
                ],
                "example": "premium",
                "type": "string"
              }
            },
            "type": "object"
          },
          "venue": {
            "nullable": true,
            "properties": {
              "foot_traffic_level": {
                "enum": [
                  "low",
                  "medium",
                  "high",
                  "very_high",
                  null
                ],
                "example": "high",
                "nullable": true,
                "type": "string"
              },
              "nearby_amenity_count": {
                "example": 57,
                "type": "integer"
              },
              "neighborhood_class": {
                "example": "dense_urban",
                "nullable": true,
                "type": "string"
              },
              "places_rating": {
                "example": 4.6,
                "nullable": true,
                "type": "number"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ScreenAvailability": {
        "description": "Per-screen capacity, booked, and remaining availability.",
        "properties": {
          "available": {
            "properties": {
              "impressions": {
                "example": 6300,
                "type": "integer"
              },
              "spots": {
                "example": 6300,
                "type": "integer"
              },
              "utilization_pct": {
                "example": 25,
                "type": "number"
              }
            },
            "type": "object"
          },
          "booked": {
            "properties": {
              "active_placements": {
                "example": 2,
                "type": "integer"
              },
              "impressions": {
                "example": 2100,
                "type": "integer"
              },
              "reserved_impressions": {
                "example": 0,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "capacity": {
            "properties": {
              "confidence": {
                "example": 0.92,
                "maximum": 1,
                "minimum": 0,
                "type": "number"
              },
              "hours": {
                "example": 168,
                "type": "number"
              },
              "impressions_per_hour": {
                "example": 50,
                "type": "integer"
              },
              "total_impressions": {
                "description": "Deliverable impressions in the window",
                "example": 8400,
                "type": "integer"
              },
              "total_spots": {
                "example": 8400,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "daily_breakdown": {
            "description": "Only present when the request sets `granularity: daily`.\n",
            "items": {
              "properties": {
                "available_impressions": {
                  "type": "integer"
                },
                "booked_impressions": {
                  "type": "integer"
                },
                "date": {
                  "format": "date",
                  "type": "string"
                },
                "reserved_impressions": {
                  "type": "integer"
                },
                "total_impressions": {
                  "type": "integer"
                }
              },
              "type": "object"
            },
            "type": "array"
          },
          "name": {
            "example": "Downtown Coffee Shop Display",
            "nullable": true,
            "type": "string"
          },
          "reliability": {
            "properties": {
              "last_seen": {
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "online_now": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "screen_id": {
            "example": "65a1b2c3d4e5f6a7b8c9d0e1",
            "type": "string"
          },
          "timezone": {
            "example": "America/New_York",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ScreenPerformance": {
        "description": "Per-screen performance metrics",
        "properties": {
          "avg_cpm": {
            "example": 3.2,
            "type": "number"
          },
          "fills": {
            "example": 2300,
            "type": "integer"
          },
          "hours_active": {
            "example": 168,
            "type": "integer"
          },
          "impressions": {
            "example": 2500,
            "type": "integer"
          },
          "screen_id": {
            "example": "507f1f77bcf86cd799439011",
            "type": "string"
          }
        },
        "type": "object"
      },
      "ScreenProfile": {
        "description": "Full screen profile returned by `GET /openrtb/v2/screens/:id`. Combines\nphysical specs, venue intelligence, live + historical audience data,\n30-day performance rollups, reliability, device capabilities, programmatic\nsettings, and brand-safety flags into a single object.\n",
        "properties": {
          "audience": {
            "properties": {
              "coverage": {
                "enum": [
                  "live",
                  "historical",
                  "none"
                ],
                "example": "live",
                "type": "string"
              },
              "estimated_daily_impressions": {
                "example": 480,
                "nullable": true,
                "type": "integer"
              },
              "live": {
                "nullable": true,
                "properties": {
                  "ad_receptivity": {
                    "example": 0.68,
                    "nullable": true,
                    "type": "number"
                  },
                  "attention_score": {
                    "example": 0.78,
                    "type": "number"
                  },
                  "crowd_density": {
                    "example": 12,
                    "nullable": true,
                    "type": "integer"
                  },
                  "data_quality": {
                    "enum": [
                      "live"
                    ],
                    "example": "live",
                    "type": "string"
                  },
                  "dominant_emotion": {
                    "example": "happy",
                    "nullable": true,
                    "type": "string"
                  },
                  "emotional_engagement": {
                    "example": 0.72,
                    "nullable": true,
                    "type": "number"
                  },
                  "face_count": {
                    "example": 4,
                    "type": "integer"
                  },
                  "last_updated": {
                    "format": "date-time",
                    "nullable": true,
                    "type": "string"
                  },
                  "purchase_intent": {
                    "example": "moderate",
                    "nullable": true,
                    "type": "string"
                  },
                  "screen_engagement": {
                    "example": 0.81,
                    "nullable": true,
                    "type": "number"
                  }
                },
                "type": "object"
              },
              "vas": {
                "nullable": true,
                "properties": {
                  "avg_raw": {
                    "example": 5.48,
                    "type": "number"
                  },
                  "avg_weighted": {
                    "example": 6.52,
                    "type": "number"
                  },
                  "cpm_multiplier": {
                    "example": 1.4,
                    "type": "number"
                  },
                  "last_measurement": {
                    "format": "date-time",
                    "nullable": true,
                    "type": "string"
                  },
                  "median": {
                    "example": 5.1,
                    "type": "number"
                  },
                  "p90": {
                    "example": 9.8,
                    "type": "number"
                  },
                  "tier": {
                    "example": "premium",
                    "type": "string"
                  },
                  "total_measurements": {
                    "example": 4820,
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "brand_safety": {
            "$ref": "#/components/schemas/BrandSafety"
          },
          "description": {
            "description": "Venue \"category — subcategory\" summary. Maps to AMP's optional \"Description\".",
            "example": "Retail — Convenience Store",
            "nullable": true,
            "type": "string"
          },
          "device": {
            "properties": {
              "capabilities": {
                "properties": {
                  "audio_classification": {
                    "type": "boolean"
                  },
                  "face_detection": {
                    "type": "boolean"
                  },
                  "sound_enabled": {
                    "type": "boolean"
                  }
                },
                "type": "object"
              },
              "connection_type": {
                "nullable": true,
                "type": "string"
              },
              "device_type": {
                "description": "OpenRTB device type (8 = CTV)",
                "example": 8,
                "type": "integer"
              },
              "make": {
                "nullable": true,
                "type": "string"
              },
              "model": {
                "nullable": true,
                "type": "string"
              },
              "os": {
                "nullable": true,
                "type": "string"
              },
              "os_version": {
                "nullable": true,
                "type": "string"
              },
              "video": {
                "properties": {
                  "max_duration_sec": {
                    "type": "integer"
                  },
                  "mimes": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "min_duration_sec": {
                    "type": "integer"
                  },
                  "protocols": {
                    "items": {
                      "type": "integer"
                    },
                    "type": "array"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "display": {
            "properties": {
              "height_px": {
                "nullable": true,
                "type": "integer"
              },
              "orientation": {
                "enum": [
                  "landscape",
                  "portrait",
                  "square"
                ],
                "nullable": true,
                "type": "string"
              },
              "sound_enabled": {
                "type": "boolean"
              },
              "width_px": {
                "nullable": true,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "location": {
            "properties": {
              "city": {
                "nullable": true,
                "type": "string"
              },
              "country": {
                "nullable": true,
                "type": "string"
              },
              "lat": {
                "nullable": true,
                "type": "number"
              },
              "lng": {
                "nullable": true,
                "type": "number"
              },
              "state": {
                "nullable": true,
                "type": "string"
              },
              "timezone": {
                "nullable": true,
                "type": "string"
              },
              "zip": {
                "nullable": true,
                "type": "string"
              }
            },
            "type": "object"
          },
          "location_description": {
            "description": "Venue-derived location summary (environment · category). Maps to AMP \"Location Description\".",
            "example": "Indoor · Retail",
            "nullable": true,
            "type": "string"
          },
          "name": {
            "example": "Downtown Coffee Shop Display",
            "type": "string"
          },
          "performance": {
            "description": "30-day VAST funnel rollup",
            "nullable": true,
            "properties": {
              "completion_rate": {
                "example": 0.942,
                "nullable": true,
                "type": "number"
              },
              "fill_rate": {
                "example": 0.867,
                "nullable": true,
                "type": "number"
              },
              "total_completions_30d": {
                "example": 13423,
                "type": "integer"
              },
              "total_impressions_30d": {
                "example": 14250,
                "type": "integer"
              },
              "vast_funnel": {
                "properties": {
                  "completions": {
                    "example": 13423,
                    "type": "integer"
                  },
                  "errors": {
                    "example": 185,
                    "type": "integer"
                  },
                  "fills": {
                    "example": 14250,
                    "type": "integer"
                  },
                  "requests": {
                    "example": 16430,
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "physical_size": {
            "$ref": "#/components/schemas/PhysicalSize"
          },
          "product_direction": {
            "description": "Maps to AMP \"Product Direction\" — \"Not Applicable\" for place-based digital screens (no roadside read-direction).",
            "example": "Not Applicable",
            "type": "string"
          },
          "programmatic": {
            "properties": {
              "allocation_pct": {
                "type": "integer"
              },
              "auction_enabled": {
                "type": "boolean"
              },
              "auction_type": {
                "example": "first_price",
                "type": "string"
              },
              "dynamic_floor_pricing": {
                "type": "boolean"
              },
              "floor_cpm": {
                "type": "number"
              },
              "header_bidding": {
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "provider": {
                    "nullable": true,
                    "type": "string"
                  },
                  "timeout_ms": {
                    "nullable": true,
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "reliability": {
            "properties": {
              "last_seen": {
                "format": "date-time",
                "nullable": true,
                "type": "string"
              },
              "online_now": {
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "screen_id": {
            "example": "65a1b2c3d4e5f6a7b8c9d0e1",
            "type": "string"
          },
          "spot_config": {
            "$ref": "#/components/schemas/SpotConfig"
          },
          "venue": {
            "properties": {
              "environment": {
                "nullable": true,
                "type": "string"
              },
              "grandchild": {
                "nullable": true,
                "type": "string"
              },
              "nearby_amenities": {
                "nullable": true,
                "properties": {
                  "bars": {
                    "example": 6,
                    "type": "integer"
                  },
                  "cafes": {
                    "example": 9,
                    "type": "integer"
                  },
                  "gyms": {
                    "example": 2,
                    "type": "integer"
                  },
                  "restaurants": {
                    "example": 14,
                    "type": "integer"
                  },
                  "shops": {
                    "example": 22,
                    "type": "integer"
                  },
                  "theatres": {
                    "example": 1,
                    "type": "integer"
                  },
                  "total": {
                    "example": 57,
                    "type": "integer"
                  },
                  "transit_stops": {
                    "example": 3,
                    "type": "integer"
                  }
                },
                "type": "object"
              },
              "nearby_amenity_count": {
                "example": 57,
                "nullable": true,
                "type": "integer"
              },
              "neighborhood_class": {
                "example": "dense_urban",
                "nullable": true,
                "type": "string"
              },
              "placement": {
                "nullable": true,
                "type": "string"
              },
              "places_price_level": {
                "example": 2,
                "nullable": true,
                "type": "integer"
              },
              "places_rating": {
                "example": 4.6,
                "nullable": true,
                "type": "number"
              },
              "places_review_count": {
                "example": 1245,
                "nullable": true,
                "type": "integer"
              },
              "quality_score": {
                "example": 0.87,
                "nullable": true,
                "type": "number"
              },
              "subcategory": {
                "nullable": true,
                "type": "string"
              },
              "type": {
                "nullable": true,
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ScreenSummary": {
        "description": "Screen summary returned in campaign creation, discover results, and\nthe default `/adslots` response. The AMP DSP-feed fields\n(`image_url`, `operating_hours`, `slot_duration_seconds`,\n`updated_at`) are surfaced at the top level so partners can hydrate\ntheir own product catalogs without a deep schema walk.\n",
        "properties": {
          "city": {
            "example": "New York",
            "nullable": true,
            "type": "string"
          },
          "country": {
            "example": "US",
            "type": "string"
          },
          "image_url": {
            "description": "Optional CDN URL to a preview/thumbnail image of the screen.\nPartners use this to render a visual representation of the\ninventory in their UI / product catalog. `null` if unset.\n",
            "example": "https://cdn.trillboards.com/screens/507f1f77bcf86cd799439011/preview.jpg",
            "nullable": true,
            "type": "string"
          },
          "location": {
            "description": "Geographic location of the screen. `lat`/`lng` (decimal degrees)\nare the authoritative, stable per-screen coordinates — prefer them\nfor distance and dayparting. `state_iso` (ISO 3166-2) and\n`postal_code` are best-effort, reverse-geocoded values and may be\nnull; if you need a precise street address, reverse-geocode from\n`lat`/`lng`.\n",
            "nullable": true,
            "properties": {
              "city": {
                "example": "New York",
                "nullable": true,
                "type": "string"
              },
              "country": {
                "description": "ISO 3166-1 alpha-2 country code.",
                "example": "US",
                "nullable": true,
                "type": "string"
              },
              "lat": {
                "example": 40.7589,
                "format": "double",
                "nullable": true,
                "type": "number"
              },
              "lng": {
                "example": -73.9851,
                "format": "double",
                "nullable": true,
                "type": "number"
              },
              "postal_code": {
                "description": "Postal/ZIP code (best-effort, reverse-geocoded; may be null).",
                "example": "10036",
                "nullable": true,
                "type": "string"
              },
              "state": {
                "example": "NY",
                "nullable": true,
                "type": "string"
              },
              "state_iso": {
                "description": "ISO 3166-2 subdivision code (best-effort; may be null).",
                "example": "US-NY",
                "nullable": true,
                "type": "string"
              },
              "timezone": {
                "description": "IANA timezone identifier.",
                "example": "America/New_York",
                "nullable": true,
                "type": "string"
              }
            },
            "type": "object"
          },
          "name": {
            "example": "Times Square Kiosk #42",
            "type": "string"
          },
          "operating_hours": {
            "description": "Optional JSON describing when the screen is online and serving\nads. Shape is partner-extensible — typical structure is\n`{ mon: { open: \"08:00\", close: \"22:00\" }, ... }` in the\nscreen's local timezone.\n",
            "example": {
              "fri": {
                "close": "23:00",
                "open": "08:00"
              },
              "mon": {
                "close": "22:00",
                "open": "08:00"
              },
              "sat": {
                "close": "23:00",
                "open": "09:00"
              },
              "sun": {
                "close": "21:00",
                "open": "09:00"
              },
              "thu": {
                "close": "22:00",
                "open": "08:00"
              },
              "tue": {
                "close": "22:00",
                "open": "08:00"
              },
              "wed": {
                "close": "22:00",
                "open": "08:00"
              }
            },
            "nullable": true,
            "type": "object"
          },
          "owner_contact": {
            "description": "Screen-owner contact (name + email). Personal data — present ONLY for\npartners granted the `contact:read` scope on their API key; omitted\nentirely for all other partners.\n",
            "nullable": true,
            "properties": {
              "email": {
                "format": "email",
                "nullable": true,
                "type": "string"
              },
              "name": {
                "nullable": true,
                "type": "string"
              }
            },
            "type": "object"
          },
          "screen_id": {
            "example": "507f1f77bcf86cd799439011",
            "type": "string"
          },
          "slot_duration_seconds": {
            "default": 15,
            "description": "Default per-spot duration in seconds. Defaults to 15s when not\nexplicitly set on the screen. Buyers should treat this as the\ntarget spot length when building creatives.\n",
            "example": 15,
            "type": "integer"
          },
          "source": {
            "description": "Inventory source partition this screen belongs to: `owned`\n(Trillboards owned + operated) or `vistar` (curated resold partner\ninventory). Surfaced on the discovery feed\n(`GET /inventory/v1/screens`); defaults to `owned`.\n",
            "enum": [
              "owned",
              "vistar"
            ],
            "example": "owned",
            "type": "string"
          },
          "updated_at": {
            "description": "ISO8601 timestamp of the last screen mutation. Use this value\non the next `/adslots?since=<updated_at>` request to fetch\nonly screens that have changed.\n",
            "example": "2026-05-11T00:00:00.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "venue_type": {
            "example": "retail",
            "nullable": true,
            "type": "string"
          }
        },
        "type": "object"
      },
      "SpotConfig": {
        "description": "DOOH-standard spot terminology derived from screen display preferences\nand programmatic allocation. Translates the impression-based model to\ndirect IO buying workflows that count spots.\n",
        "properties": {
          "ad_interval_sec": {
            "description": "Seconds between consecutive ad plays",
            "example": 30,
            "type": "integer"
          },
          "concurrent_ad_slots": {
            "description": "Number of ad slots visible simultaneously. `1` for fullscreen,\n≥1 for L-bar layouts (depends on position).\n",
            "example": 1,
            "type": "integer"
          },
          "display_mode": {
            "enum": [
              "fullscreen",
              "l-bar"
            ],
            "example": "fullscreen",
            "type": "string"
          },
          "impressions_per_hour": {
            "example": 120,
            "type": "integer"
          },
          "min_spot_duration_sec": {
            "description": "Min acceptable spot duration in seconds",
            "example": 6,
            "type": "integer"
          },
          "programmatic_share": {
            "example": 1,
            "maximum": 1,
            "minimum": 0,
            "type": "number"
          },
          "spot_duration_sec": {
            "description": "Max duration per spot in seconds",
            "example": 30,
            "type": "integer"
          },
          "spots_per_hour": {
            "description": "Spots delivered per hour (assuming 100% fill)",
            "example": 120,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "TimeseriesPoint": {
        "description": "A single data point in a time-series report",
        "properties": {
          "active_screens": {
            "example": 38,
            "type": "integer"
          },
          "avg_cpm": {
            "example": 2.45,
            "type": "number"
          },
          "impressions": {
            "example": 750,
            "type": "integer"
          },
          "timestamp": {
            "example": "2026-04-05T10:00:00Z",
            "format": "date-time",
            "type": "string"
          }
        },
        "type": "object"
      },
      "TrillboardsError": {
        "description": "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`.",
        "properties": {
          "error": {
            "properties": {
              "code": {
                "description": "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`.",
                "example": "not_found",
                "type": "string",
                "x-error-code-catalog": "#/components/schemas/ErrorCode"
              },
              "details": {
                "description": "Per-field violations (populated from Zod errors).",
                "items": {
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "param": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                },
                "type": "array"
              },
              "doc_url": {
                "description": "Documentation for this code — https://api.trillboards.com/docs/errors#<code>.",
                "example": "https://api.trillboards.com/docs/errors#AUTH_REQUIRED",
                "format": "uri",
                "type": "string"
              },
              "help": {
                "description": "Agent-facing hints and suggestions.",
                "properties": {
                  "portal_url": {
                    "format": "uri",
                    "type": "string"
                  },
                  "suggestions": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "summary": {
                    "type": "string"
                  },
                  "try_next": {
                    "type": "string"
                  }
                },
                "type": "object"
              },
              "message": {
                "description": "Human-readable error message. Safe to surface to end-users.",
                "type": "string"
              },
              "param": {
                "description": "Offending parameter name, or null when the error is not field-scoped.",
                "nullable": true,
                "type": "string"
              },
              "request_id": {
                "description": "Echoed X-Request-Id. Quote this to support for log correlation.",
                "pattern": "^req_[a-f0-9]{32}$",
                "type": "string"
              },
              "type": {
                "$ref": "#/components/schemas/ErrorType"
              }
            },
            "required": [
              "type",
              "code",
              "message",
              "request_id"
            ],
            "type": "object"
          }
        },
        "required": [
          "error"
        ],
        "type": "object",
        "x-canonical-envelope": "error"
      },
      "WebhookDelivery": {
        "description": "A single webhook delivery attempt. Schema is stable; the delivery log\ntable is finalized in a follow-up release (see `GET /webhooks/deliveries`\n`note` field until then).\n",
        "properties": {
          "attempted_at": {
            "example": "2026-04-11T14:05:12.000Z",
            "format": "date-time",
            "type": "string"
          },
          "delivery_id": {
            "example": "del_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
            "type": "string"
          },
          "duration_ms": {
            "example": 142,
            "type": "integer"
          },
          "error": {
            "description": "Error message when `status=failed`.",
            "example": null,
            "nullable": true,
            "type": "string"
          },
          "event": {
            "$ref": "#/components/schemas/WebhookEvent"
          },
          "response_body": {
            "description": "First 2 KB of the response body, truncated.",
            "example": "{\"ok\":true}",
            "nullable": true,
            "type": "string"
          },
          "response_code": {
            "example": 200,
            "nullable": true,
            "type": "integer"
          },
          "signature": {
            "description": "The `X-Trillboards-Signature` value sent with this delivery.",
            "example": "sha256=abc123...",
            "type": "string"
          },
          "status": {
            "enum": [
              "success",
              "failed"
            ],
            "example": "success",
            "type": "string"
          },
          "url": {
            "example": "https://acme-ads.com/webhooks/trillboards",
            "format": "uri",
            "type": "string"
          },
          "webhook_id": {
            "example": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
            "type": "string"
          }
        },
        "type": "object"
      },
      "WebhookEvent": {
        "description": "Supported webhook event type. Only event names in this enum are\naccepted by `POST /openrtb/v2/webhooks`.\n",
        "enum": [
          "impression.delivered",
          "impression.verified",
          "campaign.allocated",
          "campaign.started",
          "campaign.completed",
          "creative.moderation.completed",
          "screen.online",
          "screen.offline"
        ],
        "example": "impression.delivered",
        "type": "string"
      },
      "WebhookSubscription": {
        "description": "Webhook subscription record as returned by `GET /openrtb/v2/webhooks`.\nThe signing `secret` is never included — it is only shown once at\nregistration time.\n",
        "properties": {
          "created_at": {
            "example": "2026-04-11T14:00:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "example": "AdQuick staging pipeline",
            "nullable": true,
            "type": "string"
          },
          "events": {
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            },
            "type": "array"
          },
          "failure_count": {
            "example": 1,
            "type": "integer"
          },
          "last_error": {
            "description": "Most recent error message from a failed delivery. `null` when\nthe webhook has not failed.\n",
            "example": null,
            "nullable": true,
            "type": "string"
          },
          "last_triggered_at": {
            "example": "2026-04-11T14:05:12.000Z",
            "format": "date-time",
            "nullable": true,
            "type": "string"
          },
          "status": {
            "enum": [
              "active",
              "paused"
            ],
            "example": "active",
            "type": "string"
          },
          "success_count": {
            "example": 42,
            "type": "integer"
          },
          "updated_at": {
            "example": "2026-04-11T14:00:00.000Z",
            "format": "date-time",
            "type": "string"
          },
          "url": {
            "example": "https://acme-ads.com/webhooks/trillboards",
            "format": "uri",
            "type": "string"
          },
          "webhook_id": {
            "example": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
            "type": "string"
          }
        },
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "description": "Backwards-compatible header for the same centralized platform API key:\n```\nx-api-key: <platform_api_key>\n```\nPrefixes are not authorization. Existing prefixed keys continue to work.\n",
        "in": "header",
        "name": "x-api-key",
        "type": "apiKey"
      },
      "BearerAuth": {
        "description": "Canonical authentication for MCP, reporting, inventory, deals, and OpenRTB control plane:\n```\nAuthorization: Bearer <platform_api_key>\n```\nSelf-mint the key in the Earner Portal and reuse it across these surfaces.\n",
        "scheme": "bearer",
        "type": "http"
      },
      "apiKeyAuth": {
        "description": "FEIN API key with read or write permissions",
        "in": "header",
        "name": "X-API-Key",
        "type": "apiKey"
      },
      "bearerAuth": {
        "description": "JWT bearer token for protected analytics/admin routes",
        "scheme": "bearer",
        "type": "http"
      },
      "deviceTokenAuth": {
        "description": "Per-device JWT issued at pairing. This endpoint also requires\n`X-Device-Timestamp` and a unique `X-Device-Nonce`, and verifies that\nthe token's registered screen matches the `screenId` path parameter.\n",
        "in": "header",
        "name": "X-Device-Token",
        "type": "apiKey"
      },
      "dspApiKeyAuth": {
        "description": "The same platform API key, sent as a header instead. `X-API-Key` and\n`X-Trillboards-API-Key` are both accepted; `Authorization: Bearer` wins\nwhen more than one is present. Key prefixes are not authorization.\n",
        "in": "header",
        "name": "x-api-key",
        "type": "apiKey"
      },
      "dspBearerAuth": {
        "description": "Canonical credential for the `/openrtb/v2` DSP surface (inventory\ndiscovery, stats, audience intelligence):\n\n```\nAuthorization: Bearer <platform_api_key>\n```\n\nThis is a centralized **platform API key**, not the JWT that\n`bearerAuth` describes — the two are different credentials and are not\ninterchangeable. Self-mint the key in the Earner Portal; it carries the\n`DSP_DEFAULT_SCOPES` every onboarded seat gets, including\n`inventory:read` and `stats:read`.\n",
        "scheme": "bearer",
        "type": "http"
      },
      "exchangeBearerAuth": {
        "description": "The same exchange key, sent as `Authorization: Bearer <key>`.\n",
        "scheme": "bearer",
        "type": "http"
      },
      "exchangeKeyAuth": {
        "description": "The exchange key for OpenRTB 2.6 bid requests to `POST /openrtb/v1/bid`,\nissued by Trillboards to the exchange connection. A request without it,\nor with a different key, is refused `401`.\n",
        "in": "header",
        "name": "X-API-Key",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "contact": {
      "email": "api@trillboards.com",
      "name": "Trillboards API Support",
      "url": "https://trillboards.com/support"
    },
    "description": "DSP buying, programmatic serving and the FEIN signal surface.\n\nEverything you can call as a **buyer**: the DSP surface (onboarding,\ninventory discovery, bidding, creatives, campaigns, deals, reporting) and the\nprogrammatic serving surface (OpenRTB delivery, impression / event tracking,\nFEIN audience signals).\n\nTwo previously separate references, merged. Where both documented the same\nendpoint, the DSP framing is canonical — see below.\n\nAll paths are absolute against `https://api.trillboards.com`.",
    "license": {
      "name": "Proprietary",
      "url": "https://trillboards.com/terms"
    },
    "title": "Trillboards Demand API",
    "version": "1.0.0"
  },
  "openapi": "3.0.3",
  "paths": {
    "/openrtb": {
      "get": {
        "description": "Returns a human-readable catalogue of the OpenRTB surface, grouped by\ncapability, with a one-line description per route.\n\nIt is a **convenience index for people**, not a machine contract: the\nkeys are `\"<METHOD> <path>\"` display strings with Express-style `:param`\nplaceholders, the groupings are hand-maintained, and nothing guarantees\nthe list stays in step with the routes actually mounted. Do not\ndiscover endpoints from it — this specification is the contract.\n\n`version` is the API major (`v1`), not a build number.\n",
        "operationId": "openrtbServiceRoot",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "endpoints": {
                    "bidding": {
                      "GET /openrtb/v1/vast": "Generate VAST response for video ads",
                      "GET /openrtb/v1/win": "Handle win notification",
                      "POST /openrtb/v1/bid": "Process OpenRTB bid request"
                    },
                    "impressions": {
                      "GET /openrtb/v1/pixel": "Track impression via pixel",
                      "POST /openrtb/v1/impression": "Track impression with data",
                      "POST /openrtb/v1/proof-of-play": "Record MRC DOOH proof-of-play with Ed25519 signature"
                    }
                  },
                  "service": "Trillboards OpenRTB API",
                  "version": "v1"
                },
                "schema": {
                  "properties": {
                    "endpoints": {
                      "additionalProperties": {
                        "additionalProperties": {
                          "type": "string"
                        },
                        "type": "object"
                      },
                      "description": "Capability group -> `{ \"<METHOD> <path>\": \"<description>\" }`.\nGroup names and membership are hand-maintained.\n",
                      "type": "object"
                    },
                    "service": {
                      "type": "string"
                    },
                    "version": {
                      "description": "API major version, e.g. `v1`.",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Endpoint catalogue."
          }
        },
        "security": [],
        "summary": "OpenRTB service root — endpoint catalogue",
        "tags": [
          "Internal"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/bidder/{connectionAlias}/bid": {
      "post": {
        "description": "OpenRTB bidstream endpoint for offline-registered partner exchange connections.\nThis is not a platform API-key surface. The opaque `connectionAlias` resolves\n`demand_partner_config.adapter_params.partner_exchange_bidder`, applies connection admission,\nand answers through existing demand rails. SQL-controlled `initial_bid_mode=resale_waterfall`\nmaps inbound partner video/tag supply into `waterfallMediator.buildWaterfallOnly`; optional\n`resale_waterfall_partner_keys` reuses the existing per-screen waterfall override shape.\nWaterfall resale does not bid on a tag URL speculatively: explicitly allowed\npartners are tried sequentially in the explicit configured order within one\nshared inbound `tmax` budget. The first trusted VAST chain that returns a\nplayable, profitable inline creative is selected. The exact terminal\ncreative is returned in `adm` after inheriting impression, error, playback,\nand click trackers from every proven wrapper, with one render-time settlement\nbeacon. Empty/unplayable responses and timeouts return no bid and create no\npayable row.\nExplicit `resale_openrtb_partner_keys` reuses existing DPC OpenRTB demand rows,\n`OpenRTBAdapter`/`PrebidServerAdapter`, `BidCollector`, and the current OpenRTB\ncallback stores for `adm` passthrough across video, banner, audio, and native\nimpressions. Only the canonical `prebid_server` row selects PBS; PBS then uses\nits existing bidder config or the row's `adapter_params.custom_bidders`.\nThe endpoint does not auto-discover every DPC OpenRTB endpoint.\nA configured `resale_pricing_policy` is authoritative and fail-closed.\nCommercial resale requires a real positive downstream bid plus injectable\nVAST or banner HTML, explicit buyer/media/inventory identity scopes, and SQL-selected\ndownstream-minus or downstream-share pricing. The supply price must clear\nthe inbound floor and remain strictly below the rounded buyer bid. Auction\nwin is non-billable; every commercial bid returns `nurl` for auction\ntelemetry. Web banner `adm` contains the Trillboards render tracker and\nomits outer `burl`. App banner uses the distinct `openrtb_burl`\nsettlement contract and returns a signed outer `burl` that supply must\nfire only from its final-display accounting event. Video `adm` contains\nthe sole Trillboards VAST Impression and omits outer `burl`.\n`settlement_event=standard_render` lets one connection derive those\nconcrete contracts from each accepted inventory/media pair while\npreserving the same exact buyer, identity, seller-chain, and margin\ngates.\nA separately configured `resale_observation_policy` may call explicit\n`observation_openrtb_partner_keys` under stable sampling and fail-closed\nQPS limits to measure real buyer bid prices. The configured buyer keys\nmust exactly match the policy allowlist after case and duplicate\nnormalization; a mismatch is invalid and performs no buyer fanout.\nObservation always returns HTTP 204, creates no `partner_bid_outcomes`\nrow, stores no buyer notice URL, and fires no `nurl`, `burl`, or `lurl`.\nConfiguring observation and revenue policy active at the same time fails\nclosed.\nThe current bidder evaluates `imp[0]`; send one impression per request.\nAd Ops may temporarily enable bounded sanitized floor diagnostics on this\nsame DPC connection. Those samples retain top-level/PMP/quantity fields and\nexclude device IP/IFA; they do not change bidstream authentication or responses.\n`initial_bid_mode=internal_vast` remains available for booked `partner_demand_line_items`\nthat should render ordinary Trillboards inline VAST from `advertisement_mongo_id`.\nSuccessful bids include inline `adm` plus notice URLs. Waterfall-resale\nvideo omits `burl` because the Trillboards VAST wrapper contains the sole\nbillable `imp` beacon. Safe-policy OpenRTB VAST and web-banner responses\nalso omit outer `burl` because their markup contains the settlement\ntracker. App-banner `openrtb_burl` responses keep outer `burl` and do not\ninject a financial markup-load pixel.\nLegacy OpenRTB `adm` passthrough returns `burl` when upstream markup has no\nTrillboards settlement beacon; it must fire after render. VAST and legacy\nrender-time callbacks may advance `partner_bid_outcomes.served_at`\ndirectly. A safe-policy banner callback records `rendered_at`, releases\nordered buyer notices, and advances `served_at` only after billing HTTP\n2xx is durably recorded.\n",
        "operationId": "partnerExchangeBid",
        "parameters": [
          {
            "description": "Opaque partner-exchange connection alias.",
            "in": "path",
            "name": "connectionAlias",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BidRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "cur": "USD",
                  "id": "br-001",
                  "seatbid": [
                    {
                      "bid": [
                        {
                          "adid": "ad-123",
                          "adm": "<VAST version=\"3.0\">...</VAST>",
                          "burl": "https://api.trillboards.com/openrtb/bidder/example/imp?bid_request_id=br-001&imp_id=imp-1&token=opaque",
                          "crid": "cr-456",
                          "id": "bid-1",
                          "impid": "imp-1",
                          "lurl": "https://api.trillboards.com/openrtb/bidder/example/loss?bid_request_id=br-001&imp_id=imp-1&token=opaque",
                          "nurl": "https://api.trillboards.com/openrtb/bidder/example/win?bid_request_id=br-001&imp_id=imp-1&token=opaque&price=${AUCTION_PRICE}",
                          "price": 3.25
                        }
                      ],
                      "seat": "trillboards"
                    }
                  ]
                },
                "schema": {
                  "$ref": "#/components/schemas/BidResponse"
                }
              }
            },
            "description": "OpenRTB bid response with inline `adm` and notice URLs"
          },
          "204": {
            "description": "No bid, unsupported payload, cap exceeded, no priced resale source, or no renderable cleared demand"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Required request signature failed"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Source IP is not allowed for this connection"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Connection alias not found"
          }
        },
        "security": [],
        "summary": "Bid on partner exchange inventory",
        "tags": [
          "Partner Exchange Bidder"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/bidder/{connectionAlias}/imp": {
      "get": {
        "description": "Authenticated impression callback embedded inside creative markup `adm`\nfor VAST and site-banner resale. App-banner `openrtb_burl` bids return\nthis callback in the standard outer `burl`; supply must fire it only\nfrom the final SDK/display accounting event, never auction win or\ncreative prefetch. Both banner contracts first record a non-payable\nrender receipt and release the PostgreSQL win-then-billing outbox.\n`partner_bid_outcomes.served_at` advances only after durable downstream\nnotice delivery receives the required HTTP 2xx responses.\nThe outcome retains both supply and resale-demand lineage plus typed\nsupply dimensions for receivable reconciliation.\n",
        "operationId": "partnerExchangeImpressionNotice",
        "parameters": [
          {
            "in": "path",
            "name": "connectionAlias",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "bid_request_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "imp_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Internal callback capability included exactly in the in-creative/VAST tracker or app-banner `burl`.",
            "in": "query",
            "name": "token",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "HMAC signature for shared-secret notice connections.",
            "in": "query",
            "name": "sig",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Notice accepted. **The body is zero bytes** — no JSON, no\n`Content-Type`. Treat any 2xx as success.\n\nOn a connection whose settlement policy names a creative impression\nor an OpenRTB `burl`, this is the beacon that settles the play and\nenqueues the downstream buyer notice. It is idempotent with respect\nto settlement: a repeat fire for an already-served outcome is\naccepted and does not re-settle.\n"
          },
          "204": {
            "description": "The connection exists but is disabled. Zero-byte body.\n"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Missing or invalid token/signature"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Connection alias not found"
          }
        },
        "security": [],
        "summary": "Record billable partner exchange impression",
        "tags": [
          "Partner Exchange Bidder"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/bidder/{connectionAlias}/loss": {
      "get": {
        "operationId": "partnerExchangeLossNotice",
        "parameters": [
          {
            "in": "path",
            "name": "connectionAlias",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "bid_request_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "imp_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Internal callback capability included exactly as returned in `lurl`.",
            "in": "query",
            "name": "token",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "HMAC signature for shared-secret notice connections.",
            "in": "query",
            "name": "sig",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Notice accepted. **The body is zero bytes** — no JSON, no\n`Content-Type`. Treat any 2xx as success.\n\nLoss notices are recorded for auction diagnostics only. They never\nsettle revenue and never suppress a later impression notice.\n"
          },
          "204": {
            "description": "The connection exists but is disabled. Zero-byte body.\n"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Missing or invalid token/signature"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Connection alias not found"
          }
        },
        "security": [],
        "summary": "Record partner exchange loss notice",
        "tags": [
          "Partner Exchange Bidder"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/bidder/{connectionAlias}/spec": {
      "get": {
        "description": "No-secret onboarding descriptor for an offline-registered partner exchange\nconnection. This is intentionally not a platform API-key surface; the alias\nreturns bid endpoints, accepted request objects/media types, self-contained\ncallback URL behavior, billable event semantics, user-sync support,\ncopy-pasteable OpenRTB request examples, and documentation links from\n`demand_partner_config`. Returned bids also retain the inbound inventory\nobject, domain, app bundle, and country on `partner_bid_outcomes`; those\nfields join the served settlement event to downstream demand-source\nreconciliation without creating a parallel impression ledger. When a\nconfigured resale pricing policy is dormant, the descriptor explicitly\nreports HTTP 204-only behavior, no bid objects, and no billable event.\n",
        "operationId": "partnerExchangeSpec",
        "parameters": [
          {
            "description": "Opaque partner-exchange connection alias.",
            "in": "path",
            "name": "connectionAlias",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "bidstream_auth": {
                      "properties": {
                        "callback_urls": {
                          "example": "self_authenticating_returned_urls",
                          "type": "string"
                        },
                        "platform_api_key_required": {
                          "example": false,
                          "type": "boolean"
                        },
                        "request_signature_mode": {
                          "example": "none",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "billing": {
                      "properties": {
                        "bid_response_objects": {
                          "description": "Empty while pricing is dormant, observation is active, or policy configuration conflicts.",
                          "example": [
                            "adm",
                            "nurl",
                            "lurl"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "billable_impression_event": {
                          "example": "device-fired web banner render tracker or final-display app banner burl -> durable ordered buyer notices -> partner_bid_outcomes.served_at after billing HTTP 2xx; VAST settlement advances directly on its device-fired Impression; auction win is non-billable telemetry",
                          "type": "string"
                        },
                        "currency": {
                          "example": "USD",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "connection_alias": {
                      "type": "string"
                    },
                    "documentation": {
                      "properties": {
                        "openapi": {
                          "type": "string"
                        },
                        "relevant_paths": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "endpoints": {
                      "properties": {
                        "bid": {
                          "type": "string"
                        },
                        "billable_impression": {
                          "type": "string"
                        },
                        "loss": {
                          "type": "string"
                        },
                        "win": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "integration": {
                      "description": "No-secret partner integration hints generated from this connection config.",
                      "properties": {
                        "bid_request_examples": {
                          "additionalProperties": {
                            "type": "object"
                          },
                          "description": "OpenRTB examples keyed by media/object shape, never credentials.",
                          "type": "object"
                        },
                        "billable_impression": {
                          "example": "fire the in-creative impression tracker on real render; do not bill nurl/win",
                          "type": "string"
                        },
                        "content_type": {
                          "example": "application/json",
                          "type": "string"
                        },
                        "no_bid_status": {
                          "example": 204,
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "inventory": {
                      "properties": {
                        "accepted_channels": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "accepted_media_types": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "accepted_objects": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "required_seller_signals": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "observation_policy": {
                      "description": "Non-commercial downstream liquidity observation state.\nActive observation may call explicitly scoped buyers but\nalways returns HTTP 204 and cannot create payable or notice\nside effects. Invalid includes a mismatch between configured\nobservation buyer keys and the policy buyer allowlist and\nperforms no buyer fanout. Conflict means pricing and\nobservation were both enabled, so the bid endpoint fails\nclosed.\n",
                      "nullable": true,
                      "properties": {
                        "bid_behavior": {
                          "enum": [
                            "always_no_bid_204"
                          ],
                          "type": "string"
                        },
                        "creates_payable_outcome": {
                          "example": false,
                          "type": "boolean"
                        },
                        "downstream_notices_emitted": {
                          "example": false,
                          "type": "boolean"
                        },
                        "status": {
                          "enum": [
                            "active",
                            "dormant",
                            "invalid",
                            "conflict"
                          ],
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "onboarding": {
                      "properties": {
                        "initial_bid_mode": {
                          "example": "resale_waterfall",
                          "type": "string"
                        },
                        "requires_active_partner_demand_line": {
                          "example": false,
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    },
                    "openrtb_version": {
                      "type": "string"
                    },
                    "partner_key": {
                      "type": "string"
                    },
                    "reporting": {
                      "properties": {
                        "partner_exchange_stats_endpoint": {
                          "example": null,
                          "nullable": true,
                          "type": "string"
                        },
                        "use_platform_api_reporting": {
                          "example": true,
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    },
                    "resale_policy": {
                      "description": "Downstream-first resale state when this connection has a\nconfigured pricing policy. Dormant means every valid bid\nrequest returns HTTP 204 before buyer fanout. Conflict\nmeans revenue and observation were both enabled, so the\nbid endpoint also fails closed with HTTP 204.\n",
                      "nullable": true,
                      "properties": {
                        "bid_behavior": {
                          "enum": [
                            "downstream_price_required",
                            "no_bid_204"
                          ],
                          "type": "string"
                        },
                        "settlement_event": {
                          "description": "`standard_render` derives the concrete contract for\neach response: `vast_impression` is video-only;\n`creative_impression` is site-banner-only and begins\nfrom an in-markup render beacon; `openrtb_burl` is\napp-banner-only and begins from final-display supply\naccounting. Both banner contracts settle only after\ntheir ordered buyer billing notice returns HTTP 2xx.\n",
                          "enum": [
                            "standard_render",
                            "vast_impression",
                            "creative_impression",
                            "openrtb_burl"
                          ],
                          "nullable": true,
                          "type": "string"
                        },
                        "settlement_events": {
                          "description": "Concrete media-specific events advertised only when the active policy uses `standard_render`.",
                          "items": {
                            "enum": [
                              "vast_impression",
                              "creative_impression",
                              "openrtb_burl"
                            ],
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "status": {
                          "enum": [
                            "active",
                            "dormant",
                            "conflict"
                          ],
                          "type": "string"
                        },
                        "supply_win_notice_emitted": {
                          "example": true,
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    },
                    "response_capabilities": {
                      "properties": {
                        "bid_response_media_types": {
                          "example": [
                            "video",
                            "banner",
                            "audio",
                            "native"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "bid_response_modes": {
                          "example": [
                            "video_vast",
                            "openrtb_adm_passthrough"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "seat": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "user_sync": {
                      "properties": {
                        "cookie_lifetime": {
                          "nullable": true,
                          "type": "string"
                        },
                        "supported": {
                          "example": false,
                          "type": "boolean"
                        },
                        "sync_url": {
                          "nullable": true,
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Connection-specific bidstream contract"
          },
          "204": {
            "description": "Connection exists but is not sandbox/active"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Connection alias not found"
          }
        },
        "security": [],
        "summary": "Get partner exchange OpenRTB connection spec",
        "tags": [
          "Partner Exchange Bidder"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/bidder/{connectionAlias}/win": {
      "get": {
        "description": "Win callback generated in OpenRTB `nurl`. When the connection has no\nshared notice secret, the returned URL is self-authenticating; when a\nnotice secret is configured, the returned URL carries `sig`. A win is\nauction telemetry only and never creates a payable impression.\n",
        "operationId": "partnerExchangeWinNotice",
        "parameters": [
          {
            "in": "path",
            "name": "connectionAlias",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "bid_request_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "imp_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Expanded `${AUCTION_PRICE}` CPM when supplied by the exchange.",
            "in": "query",
            "name": "price",
            "schema": {
              "type": "number"
            }
          },
          {
            "description": "Internal callback capability included exactly as returned in `nurl`.",
            "in": "query",
            "name": "token",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "HMAC signature for shared-secret notice connections.",
            "in": "query",
            "name": "sig",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Notice accepted. **The body is zero bytes** — there is no JSON, no\n`Content-Type`, and therefore nothing to parse. Treat any 2xx as\nsuccess and do not branch on the body.\n\nA win notice is auction telemetry and is **not** a billing event;\nsettlement happens on the impression beacon or the render tracker,\ndepending on the connection's configured policy. See\n`GET /openrtb/bidder/{connectionAlias}/spec` for which event settles\non your connection.\n"
          },
          "204": {
            "description": "The connection exists but is disabled. Also a zero-byte body. Stop\nfiring notices for this alias until it is re-enabled.\n"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Missing or invalid token/signature"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Connection alias not found"
          }
        },
        "security": [],
        "summary": "Record partner exchange win notice",
        "tags": [
          "Partner Exchange Bidder"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/health": {
      "get": {
        "description": "Liveness only.\n\n**This probe checks no downstream dependency.** It does not touch\nPostgreSQL, ClickHouse or Redis; it reports that the Node process is\nanswering and returns its own heap and uptime. A `200` here therefore\nmeans the router is mounted and the event loop is turning, and says\nnothing about whether an ad can actually be served. Do not use it as a\nreadiness gate for the serving path.\n\n`status` at the top level is the literal `ok` on every response.\n`memory.status` is the only field that varies with health: `healthy`\nbelow 75% heap use, `warning` above 75%, `critical` above 90%. It is\ncomputed against `heapTotal`, which Node grows on demand, so a high\npercentage is not by itself evidence of a leak.\n",
        "operationId": "openrtbHealth",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "memory": {
                    "heap_mb": "412/604",
                    "heap_pct": 68.2,
                    "status": "healthy",
                    "uptime_hrs": 51.34
                  },
                  "service": "Trillboards OpenRTB API",
                  "status": "ok",
                  "timestamp": "2026-08-31T09:40:00.004Z",
                  "version": "v1"
                },
                "schema": {
                  "properties": {
                    "memory": {
                      "properties": {
                        "heap_mb": {
                          "description": "`\"<used>/<total>\"` in whole megabytes.",
                          "type": "string"
                        },
                        "heap_pct": {
                          "description": "Used share of `heapTotal`, one decimal place.",
                          "format": "double",
                          "type": "number"
                        },
                        "status": {
                          "enum": [
                            "healthy",
                            "warning",
                            "critical"
                          ],
                          "type": "string"
                        },
                        "uptime_hrs": {
                          "description": "Process uptime in hours, two decimal places.",
                          "format": "double",
                          "type": "number"
                        }
                      },
                      "type": "object"
                    },
                    "service": {
                      "type": "string"
                    },
                    "status": {
                      "enum": [
                        "ok"
                      ],
                      "type": "string"
                    },
                    "timestamp": {
                      "format": "date-time",
                      "type": "string"
                    },
                    "version": {
                      "description": "API major version, e.g. `v1`.",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The process is answering. This is the only status this operation\nreturns — there is no dependency check that could fail it.\n"
          }
        },
        "security": [],
        "summary": "OpenRTB service liveness probe",
        "tags": [
          "Internal"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/adpod/{token}": {
      "get": {
        "description": "Ad pods (multiple back-to-back ad slots in one break) are fulfilled\nvia a signed token referencing a pre-built waterfall. Screens redeem\nthe token here to receive the full VAST document with multiple\n`<Ad>` elements in serving order. Single-use, expires after 60\nseconds.\n",
        "operationId": "resolveAdPod",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Ad-pod VAST 4.2 XML document"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Token expired, already redeemed, or never issued"
          }
        },
        "security": [],
        "summary": "Resolve an ad-pod fulfilment token",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/analytics/events": {
      "get": {
        "operationId": "getEventAnalytics",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "performanceMetrics": {
                    "avgCPU": 21.6,
                    "avgFPS": 59.4,
                    "avgLatency": 38,
                    "avgLoadTime": 412.7,
                    "avgMemory": 314.9,
                    "avgRenderTime": 88.2,
                    "sampleCount": 1204,
                    "totalErrors": 3
                  },
                  "recentEvents": [
                    {
                      "adId": "64f000000000000000000001",
                      "eventType": "complete",
                      "metadata": {
                        "impression_id": "imp_pop_1"
                      },
                      "screenId": "507f1f77bcf86cd799439022",
                      "timestamp": "2026-08-30T23:58:40.000Z"
                    }
                  ],
                  "summary": [
                    {
                      "_id": 0,
                      "count": 41892,
                      "eventType": "complete",
                      "firstEvent": "2026-08-01T06:02:11.000Z",
                      "lastEvent": "2026-08-30T23:58:40.000Z",
                      "uniqueAdsCount": 26,
                      "uniqueScreensCount": 118
                    },
                    {
                      "_id": 0,
                      "count": 44160,
                      "eventType": "start",
                      "firstEvent": "2026-08-01T06:02:06.000Z",
                      "lastEvent": "2026-08-30T23:58:35.000Z",
                      "uniqueAdsCount": 26,
                      "uniqueScreensCount": 121
                    }
                  ]
                },
                "schema": {
                  "properties": {
                    "performanceMetrics": {
                      "description": "See above — `null` in two different situations.",
                      "nullable": true,
                      "properties": {
                        "avgCPU": {
                          "format": "double",
                          "type": "number"
                        },
                        "avgFPS": {
                          "format": "double",
                          "type": "number"
                        },
                        "avgLatency": {
                          "format": "double",
                          "type": "number"
                        },
                        "avgLoadTime": {
                          "format": "double",
                          "type": "number"
                        },
                        "avgMemory": {
                          "format": "double",
                          "type": "number"
                        },
                        "avgRenderTime": {
                          "format": "double",
                          "type": "number"
                        },
                        "sampleCount": {
                          "description": "Samples behind the averages. Zero means they are meaningless.",
                          "type": "integer"
                        },
                        "totalErrors": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "recentEvents": {
                      "items": {
                        "properties": {
                          "adId": {
                            "nullable": true,
                            "type": "string"
                          },
                          "eventType": {
                            "type": "string"
                          },
                          "metadata": {
                            "type": "object"
                          },
                          "screenId": {
                            "nullable": true,
                            "type": "string"
                          },
                          "timestamp": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "summary": {
                      "description": "One entry per distinct event type in the window.",
                      "items": {
                        "properties": {
                          "_id": {
                            "description": "Always `0`. Not an identifier; ignore it.",
                            "type": "integer"
                          },
                          "count": {
                            "type": "integer"
                          },
                          "eventType": {
                            "type": "string"
                          },
                          "firstEvent": {
                            "format": "date-time",
                            "nullable": true,
                            "type": "string"
                          },
                          "lastEvent": {
                            "format": "date-time",
                            "nullable": true,
                            "type": "string"
                          },
                          "uniqueAdsCount": {
                            "type": "integer"
                          },
                          "uniqueScreensCount": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Counts grouped by event type, a recent-event tail, and — only\nsometimes — performance metrics.\n\n`performanceMetrics` is `null` unless you either omit `eventType`\nor set it to `performance`; any other filter returns `null` rather\nthan an empty object. It is **also** `null` when no performance\nsamples exist in the window, so `null` does not distinguish \"not\nrequested\" from \"nothing recorded\".\n\n`summary[]._id` is a constant `0` — a serialisation artifact, not\nan identifier. Group on `eventType`.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get event analytics",
        "tags": [
          "Event Tracking"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/analytics/impressions": {
      "get": {
        "operationId": "getImpressionAnalytics",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "impressions": [
                    {
                      "_id": "imp_pop_1",
                      "adId": "64f000000000000000000001",
                      "adSource": "programmatic",
                      "allocationId": null,
                      "deviceId": "P_0086ea619438637e1c92cf35",
                      "displayContext": {
                        "displayAreaRatio": 1,
                        "displayMode": "fullscreen",
                        "lBarPosition": null,
                        "lBarSize": 0,
                        "lBarSlotCount": null,
                        "lBarSlotIndex": null
                      },
                      "impressionId": "imp_pop_1",
                      "metadata": {},
                      "screenId": "507f1f77bcf86cd799439022",
                      "timestamp": "2026-08-30T14:12:03.118Z"
                    },
                    {
                      "_id": "imp_pop_0",
                      "adId": "64f000000000000000000001",
                      "adSource": "programmatic",
                      "allocationId": null,
                      "deviceId": "P_0086ea619438637e1c92cf35",
                      "displayContext": {
                        "displayAreaRatio": 1,
                        "displayMode": "fullscreen",
                        "lBarPosition": null,
                        "lBarSize": 0,
                        "lBarSlotCount": null,
                        "lBarSlotIndex": null
                      },
                      "impressionId": "imp_pop_0",
                      "metadata": {},
                      "screenId": "507f1f77bcf86cd799439022",
                      "timestamp": "2026-08-30T14:11:48.002Z"
                    }
                  ],
                  "summary": {
                    "avgViewableTime": 0,
                    "totalImpressions": 2,
                    "viewabilityRate": "0.00",
                    "viewableImpressions": 0
                  },
                  "viewabilityMetrics": []
                },
                "schema": {
                  "properties": {
                    "impressions": {
                      "items": {
                        "properties": {
                          "_id": {
                            "description": "Same value as `impressionId`.",
                            "type": "string"
                          },
                          "adId": {
                            "type": "string"
                          },
                          "adSource": {
                            "description": "How the ad was sourced, e.g. `programmatic` or `internal`.",
                            "type": "string"
                          },
                          "allocationId": {
                            "nullable": true,
                            "type": "string"
                          },
                          "deviceId": {
                            "nullable": true,
                            "type": "string"
                          },
                          "displayContext": {
                            "description": "Always present and fully defaulted — `displayMode`\nfalls back to `fullscreen` and `displayAreaRatio` to\n`1.0`, so a default here does not mean the screen\nreported one.\n",
                            "properties": {
                              "displayAreaRatio": {
                                "format": "double",
                                "type": "number"
                              },
                              "displayMode": {
                                "type": "string"
                              },
                              "lBarPosition": {
                                "nullable": true,
                                "type": "string"
                              },
                              "lBarSize": {
                                "type": "integer"
                              },
                              "lBarSlotCount": {
                                "nullable": true,
                                "type": "integer"
                              },
                              "lBarSlotIndex": {
                                "nullable": true,
                                "type": "integer"
                              }
                            },
                            "type": "object"
                          },
                          "impressionId": {
                            "type": "string"
                          },
                          "metadata": {
                            "type": "object"
                          },
                          "screenId": {
                            "type": "string"
                          },
                          "timestamp": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "summary": {
                      "properties": {
                        "avgViewableTime": {
                          "description": "Always zero.",
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            }
                          ]
                        },
                        "totalImpressions": {
                          "description": "Number of rows returned, not a campaign lifetime total.",
                          "type": "integer"
                        },
                        "viewabilityRate": {
                          "description": "Always zero; string or number depending on branch.",
                          "oneOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            }
                          ]
                        },
                        "viewableImpressions": {
                          "description": "Always `0` — see above.",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "viewabilityMetrics": {
                      "description": "Always empty. Retained for response-shape compatibility.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Impression rows plus a summary block.\n\n## The viewability half of this response is permanently empty\n\n`viewabilityMetrics` is a hardcoded empty array: the collection that\nbacked it was Mongo-only and has been retired, and no replacement is\nwired in here. Everything derived from it is therefore constant —\n`summary.viewableImpressions` is always `0`,\n`summary.avgViewableTime` is always `0`, and\n`summary.viewabilityRate` is always zero. Read viewability from\n`POST /openrtb/v1/viewability`'s own storage instead; do not report\nthese three as a measured zero.\n\n`summary.viewabilityRate` is also **not a stable JSON type**: it is\nthe string `\"0.00\"` when at least one impression matched, and the\nnumber `0` when none did. Parse it leniently.\n\nThe row list is capped at 1000 server-side and is not paginated —\nthere is no cursor and no total. Narrow with `startDate`/`endDate`.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get impression analytics",
        "tags": [
          "Impression Tracking"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/analytics/interactions": {
      "get": {
        "operationId": "getInteractionAnalytics",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "analytics": {
                    "interactions": [],
                    "summary": {
                      "byType": []
                    },
                    "totalInteractions": 0
                  },
                  "pagination": {
                    "hasMore": false,
                    "limit": 100,
                    "offset": 0,
                    "total": 0
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "analytics": {
                      "properties": {
                        "interactions": {
                          "description": "Always empty.",
                          "items": {
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "summary": {
                          "properties": {
                            "byType": {
                              "description": "Always empty.",
                              "items": {
                                "type": "object"
                              },
                              "type": "array"
                            },
                            "engagementRate": {
                              "description": "Present only when `adId` is supplied and that ad\nhas recorded impressions. Always `0`, being\nderived from the constant total above.\n",
                              "format": "double",
                              "type": "number"
                            }
                          },
                          "type": "object"
                        },
                        "totalInteractions": {
                          "description": "Always `0`.",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "pagination": {
                      "properties": {
                        "hasMore": {
                          "description": "Always `false`.",
                          "type": "boolean"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Interaction totals and a type breakdown.\n\n## This surface reports zero and always will\n\nThe collection it aggregated was Mongo-only and is retired;\nPostgreSQL `interactions` is canonical and nothing here reads it.\nEvery count is a hardcoded literal, so the response below is not\n\"no data in your window\" — it is the only response this operation\ncan produce, for any window, for any account.\n\nDo not chart it, alert on it, or report it as measured zero\nengagement.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get interaction analytics",
        "tags": [
          "Interaction Tracking"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/analytics/qr-scans": {
      "get": {
        "operationId": "getQrScanAnalytics",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "analytics": {
                    "query": {
                      "adId": "64f000000000000000000001",
                      "groupBy": "day"
                    },
                    "scansByPeriod": [],
                    "topPerformingAds": [],
                    "totalScans": 0
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "analytics": {
                      "properties": {
                        "query": {
                          "description": "Your filters echoed back. Keys you did not send are\nabsent, not null.\n",
                          "properties": {
                            "adId": {
                              "type": "string"
                            },
                            "endDate": {
                              "type": "string"
                            },
                            "groupBy": {
                              "description": "Defaults to `day`.",
                              "type": "string"
                            },
                            "screenId": {
                              "type": "string"
                            },
                            "startDate": {
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "scansByPeriod": {
                          "description": "Always empty.",
                          "items": {
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "topPerformingAds": {
                          "description": "Always empty.",
                          "items": {
                            "properties": {
                              "adId": {
                                "type": "string"
                              },
                              "adName": {
                                "type": "string"
                              },
                              "advertiserId": {
                                "type": "string"
                              },
                              "scanCount": {
                                "type": "integer"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "totalScans": {
                          "description": "Always `0`.",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "QR scan totals, a period series, and a top-ads leaderboard.\n\n## This surface reports zero and always will\n\nThe collection it aggregated was Mongo-only and is retired;\nPostgreSQL `interactions` is canonical and nothing here reads it.\nEvery count is a hardcoded literal, so the response below is not\n\"no data in your window\" — it is the only response this operation\ncan produce, for any window, for any account.\n\nDo not chart it, alert on it, or report it as measured zero\nengagement.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get QR scan analytics",
        "tags": [
          "Interaction Tracking"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/bandwidth-metric": {
      "post": {
        "description": "Reports a screen's daily bandwidth consumption and cache effectiveness,\nsplit between ad video and content video.\n\n## Counters accumulate, they are not set\n\nThe row is keyed on `(screen_id, date, connection_type)` where `date` is\nalways the **server's** current date — it is not an input. On a repeat\ncall for the same key, all eight counters are **added** to what is\nalready stored; the descriptive fields are filled in only where they were\npreviously null. So send deltas since your last call, never running\ntotals, or the day's figures will compound. A retry after an ambiguous\nfailure will double-count.\n\nNote that `connection_type` is part of the key, so a screen that switches\nfrom wifi to cellular mid-day accumulates into two separate rows.\n\nOnly `application/json` is parsed; a `sendBeacon` defaulting to\n`text/plain` arrives with an empty body and is rejected as a missing\n`screen_id`.\n",
        "operationId": "recordBandwidthMetric",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "ad_video_bytes_cached": 5000000,
                "ad_video_bytes_fetched": 1000000,
                "ad_video_cache_hits": 10,
                "ad_video_cache_misses": 2,
                "bandwidth_mbps": 9.7,
                "connection_type": "wifi",
                "content_cache_bytes_fetched": 3000000,
                "content_cache_bytes_saved": 20000000,
                "content_cache_hits": 50,
                "content_cache_misses": 5,
                "device_class": "tablet",
                "device_id": "dev-1",
                "effective_type": "4g",
                "latency_rtt_ms": 171,
                "save_data": true,
                "screen_id": "scr-1"
              },
              "schema": {
                "properties": {
                  "ad_video_bytes_cached": {
                    "description": "Bytes served from cache. Defaults to 0. Accumulates.",
                    "example": 5000000,
                    "format": "int64",
                    "type": "integer"
                  },
                  "ad_video_bytes_fetched": {
                    "description": "Bytes pulled over the network. Defaults to 0. Accumulates.",
                    "example": 1000000,
                    "format": "int64",
                    "type": "integer"
                  },
                  "ad_video_cache_hits": {
                    "description": "Defaults to 0. Accumulates.",
                    "example": 10,
                    "type": "integer"
                  },
                  "ad_video_cache_misses": {
                    "description": "Defaults to 0. Accumulates.",
                    "example": 2,
                    "type": "integer"
                  },
                  "bandwidth_mbps": {
                    "description": "Downlink estimate. A non-finite value is stored as null\nrather than rejected.\n",
                    "example": 9.7,
                    "format": "double",
                    "type": "number"
                  },
                  "connection_type": {
                    "description": "Defaults to `unknown`. **Part of the accumulation key** — a\ndifferent value writes to a different row for the same screen\nand day.\n",
                    "example": "wifi",
                    "type": "string"
                  },
                  "content_cache_bytes_fetched": {
                    "description": "Defaults to 0. Accumulates.",
                    "example": 3000000,
                    "format": "int64",
                    "type": "integer"
                  },
                  "content_cache_bytes_saved": {
                    "description": "Defaults to 0. Accumulates.",
                    "example": 20000000,
                    "format": "int64",
                    "type": "integer"
                  },
                  "content_cache_hits": {
                    "description": "Defaults to 0. Accumulates.",
                    "example": 50,
                    "type": "integer"
                  },
                  "content_cache_misses": {
                    "description": "Defaults to 0. Accumulates.",
                    "example": 5,
                    "type": "integer"
                  },
                  "device_class": {
                    "description": "Defaults to `unknown`.",
                    "example": "tablet",
                    "type": "string"
                  },
                  "device_id": {
                    "example": "dev-1",
                    "type": "string"
                  },
                  "effective_type": {
                    "description": "Network Information API effective type.",
                    "example": "4g",
                    "type": "string"
                  },
                  "latency_rtt_ms": {
                    "description": "Round-trip estimate. Non-finite values store as null.",
                    "example": 171,
                    "type": "integer"
                  },
                  "save_data": {
                    "description": "Data-saver preference. Must be a real boolean — the string\n`\"true\"` is silently stored as null.\n",
                    "example": true,
                    "type": "boolean"
                  },
                  "screen_id": {
                    "description": "Screen reporting the usage.",
                    "example": "scr-1",
                    "type": "string"
                  }
                },
                "required": [
                  "screen_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "success": true
                },
                "schema": {
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Metric accumulated. The body is exactly `{\"success\": true}`.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_required_field",
                    "doc_url": "https://api.trillboards.com/docs/errors#missing_required_field",
                    "message": "screen_id is required.",
                    "param": "screen_id",
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "invalid_request_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "`screen_id` is missing."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "internal_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#internal_error",
                    "message": "An internal error occurred. Please contact support with request_id req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The write failed. Unlike the VAST cache metric, this endpoint's write\nis **not** best-effort — a database error surfaces here, so nothing\nwas accumulated and a retry is safe.\n"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "service_unavailable",
                    "doc_url": "https://api.trillboards.com/docs/errors#service_unavailable",
                    "message": "PostgreSQL is temporarily unavailable.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The PostgreSQL backend is unavailable. Retry with backoff."
          }
        },
        "security": [],
        "summary": "Track bandwidth telemetry",
        "tags": [
          "Bandwidth Telemetry"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/bandwidth/analytics": {
      "get": {
        "description": "Fleet-wide bandwidth savings over a date range, with daily, device-class,\nand connection-type breakdowns.\n\n## Numeric fields in the breakdowns are JSON strings\n\nOnly `summary` is coerced to numbers. `daily`, `by_device_class`, and\n`by_connection_type` are passed through from the database unmodified, so\nevery sum and count in them arrives as a **string** (`\"1000\"`, not\n`1000`) because the underlying columns are 64-bit integers. `date` in\n`daily` likewise serialises as a full ISO-8601 timestamp, not a bare\ncalendar date. Coerce before doing arithmetic, and do not type these as\nintegers in a generated client.\n\n`hit_rate_pct` here is a percentage to **two** decimal places — a third\nrounding convention alongside the fraction used by the VAST request\nanalytics and the one-decimal percentage used by the cache analytics.\n\nDates are passed straight into the query as `date` values, so an\nunparseable `start_date` produces a `500` rather than falling back.\n",
        "operationId": "getBandwidthAnalytics",
        "parameters": [
          {
            "description": "Inclusive start, `YYYY-MM-DD`. Defaults to 7 days ago.",
            "example": "2026-08-25",
            "in": "query",
            "name": "start_date",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Inclusive end, `YYYY-MM-DD`. Defaults to today.",
            "example": "2026-09-01",
            "in": "query",
            "name": "end_date",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Restricts every block, including the breakdowns.",
            "example": "tablet",
            "in": "query",
            "name": "device_class",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Restricts every block to one screen.",
            "in": "query",
            "name": "screen_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Restricts every block to one connection type.",
            "example": "wifi",
            "in": "query",
            "name": "connection_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "by_connection_type": [
                    {
                      "bytes_fetched": "300",
                      "bytes_saved": "800",
                      "cache_hits": "8",
                      "cache_misses": "1",
                      "connection_type": "wifi",
                      "content_bytes_fetched": "200",
                      "content_bytes_saved": "1500",
                      "screen_count": "1"
                    }
                  ],
                  "by_device_class": [
                    {
                      "bytes_fetched": "500",
                      "bytes_saved": "1000",
                      "cache_hits": "10",
                      "cache_misses": "2",
                      "content_bytes_fetched": "300",
                      "content_bytes_saved": "2000",
                      "device_class": "tablet",
                      "screen_count": "1"
                    }
                  ],
                  "daily": [
                    {
                      "bytes_fetched": "500",
                      "bytes_saved": "1000",
                      "cache_hits": "10",
                      "cache_misses": "2",
                      "content_bytes_fetched": "300",
                      "content_bytes_saved": "2000",
                      "content_hits": "50",
                      "content_misses": "5",
                      "date": "2026-03-01T00:00:00.000Z",
                      "screen_count": "1"
                    }
                  ],
                  "period": {
                    "end": "2026-09-01",
                    "start": "2026-08-25"
                  },
                  "summary": {
                    "content_cache": {
                      "gb_fetched": 2.793967723846436e-7,
                      "gb_saved": 0.000001862645149230957,
                      "total_bytes_fetched": 300,
                      "total_bytes_saved": 2000
                    },
                    "estimated_savings_usd": 7.916241884231567e-8,
                    "gb_fetched": 4.6566128730773926e-7,
                    "gb_saved": 9.313225746154785e-7,
                    "hit_rate_pct": 83.33,
                    "total_bytes_fetched": 500,
                    "total_bytes_saved": 1000,
                    "total_hits": 10,
                    "total_misses": 2,
                    "total_screens": 1
                  }
                },
                "schema": {
                  "properties": {
                    "by_connection_type": {
                      "description": "Raw database rows — every numeric field is a string. Ordered\nby bytes fetched descending.\n",
                      "items": {
                        "properties": {
                          "bytes_fetched": {
                            "type": "string"
                          },
                          "bytes_saved": {
                            "type": "string"
                          },
                          "cache_hits": {
                            "type": "string"
                          },
                          "cache_misses": {
                            "type": "string"
                          },
                          "connection_type": {
                            "type": "string"
                          },
                          "content_bytes_fetched": {
                            "type": "string"
                          },
                          "content_bytes_saved": {
                            "type": "string"
                          },
                          "screen_count": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "by_device_class": {
                      "description": "Raw database rows — every numeric field is a string. Ordered\nby bytes saved descending.\n",
                      "items": {
                        "properties": {
                          "bytes_fetched": {
                            "type": "string"
                          },
                          "bytes_saved": {
                            "type": "string"
                          },
                          "cache_hits": {
                            "type": "string"
                          },
                          "cache_misses": {
                            "type": "string"
                          },
                          "content_bytes_fetched": {
                            "type": "string"
                          },
                          "content_bytes_saved": {
                            "type": "string"
                          },
                          "device_class": {
                            "type": "string"
                          },
                          "screen_count": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "daily": {
                      "description": "Raw database rows — every numeric field is a string.\n",
                      "items": {
                        "properties": {
                          "bytes_fetched": {
                            "type": "string"
                          },
                          "bytes_saved": {
                            "type": "string"
                          },
                          "cache_hits": {
                            "type": "string"
                          },
                          "cache_misses": {
                            "type": "string"
                          },
                          "content_bytes_fetched": {
                            "type": "string"
                          },
                          "content_bytes_saved": {
                            "type": "string"
                          },
                          "content_hits": {
                            "type": "string"
                          },
                          "content_misses": {
                            "type": "string"
                          },
                          "date": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "screen_count": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "period": {
                      "description": "Echo of the effective range, as the raw date strings.",
                      "properties": {
                        "end": {
                          "type": "string"
                        },
                        "start": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "summary": {
                      "description": "The only block whose numbers are JSON numbers.",
                      "properties": {
                        "content_cache": {
                          "description": "Content-video half, broken out.",
                          "properties": {
                            "gb_fetched": {
                              "format": "double",
                              "type": "number"
                            },
                            "gb_saved": {
                              "format": "double",
                              "type": "number"
                            },
                            "total_bytes_fetched": {
                              "format": "int64",
                              "type": "integer"
                            },
                            "total_bytes_saved": {
                              "format": "int64",
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        },
                        "estimated_savings_usd": {
                          "description": "Flat $0.085/GB egress assumption. Unrounded.",
                          "format": "double",
                          "type": "number"
                        },
                        "gb_fetched": {
                          "format": "double",
                          "type": "number"
                        },
                        "gb_saved": {
                          "format": "double",
                          "type": "number"
                        },
                        "hit_rate_pct": {
                          "description": "Percentage 0-100, two decimal places.",
                          "format": "double",
                          "type": "number"
                        },
                        "total_bytes_fetched": {
                          "format": "int64",
                          "type": "integer"
                        },
                        "total_bytes_saved": {
                          "format": "int64",
                          "type": "integer"
                        },
                        "total_hits": {
                          "type": "integer"
                        },
                        "total_misses": {
                          "type": "integer"
                        },
                        "total_screens": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Bandwidth savings for the requested range."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Admin access required",
                  "statusCode": 403
                },
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "statusCode": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Authenticated but not an operator. This response is unscoped fleet-wide —\nthe handler applies no tenant or screen filter — so it requires the admin\nrole, not merely a session.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "internal_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#internal_error",
                    "message": "An internal error occurred. Please contact support with request_id req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The query failed, or a supplied date could not be parsed."
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "service_unavailable",
                    "doc_url": "https://api.trillboards.com/docs/errors#service_unavailable",
                    "message": "PostgreSQL is temporarily unavailable.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The PostgreSQL backend is unavailable. Retry with backoff."
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get aggregated bandwidth analytics",
        "tags": [
          "Bandwidth Telemetry"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/bandwidth/screen-usage": {
      "get": {
        "description": "Bandwidth and cache history for a single screen, broken out by day and\nsummarised per connection type. The per-screen counterpart to\n`GET /openrtb/v1/bandwidth/analytics`.\n\nBoth arrays are returned exactly as the database produced them, so —\nas on that endpoint — **the 64-bit and fixed-point numeric fields arrive\nas JSON strings**, and `date` is a full ISO-8601 timestamp. The two\nplain-integer counters in `daily` (`total_cache_hits`,\n`total_cache_misses`) are the exception and come back as numbers; in\n`summary_by_connection_type` even those are strings, because they are\nsummed. Coerce before doing arithmetic.\n\nEach `daily` row is one `(date, connection_type)` pair, so a screen that\nchanged connection during a day contributes more than one row for that\nday.\n",
        "operationId": "getScreenBandwidthUsage",
        "parameters": [
          {
            "description": "Screen to report on.",
            "example": "scr-1",
            "in": "query",
            "name": "screen_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Inclusive start, `YYYY-MM-DD`. Defaults to 30 days ago.",
            "example": "2026-08-02",
            "in": "query",
            "name": "start_date",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Inclusive end, `YYYY-MM-DD`. Defaults to today.",
            "example": "2026-09-01",
            "in": "query",
            "name": "end_date",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "daily": [
                    {
                      "cache_hit_rate_pct": "85.7",
                      "connection_type": "wifi",
                      "date": "2026-03-01T00:00:00.000Z",
                      "total_bytes_downloaded": "2000",
                      "total_bytes_saved_by_cache": "5000",
                      "total_cache_hits": 30,
                      "total_cache_misses": 5
                    },
                    {
                      "cache_hit_rate_pct": "83.3",
                      "connection_type": "cellular",
                      "date": "2026-03-01T00:00:00.000Z",
                      "total_bytes_downloaded": "500",
                      "total_bytes_saved_by_cache": "1000",
                      "total_cache_hits": 10,
                      "total_cache_misses": 2
                    }
                  ],
                  "period": {
                    "end": "2026-09-01",
                    "start": "2026-08-02"
                  },
                  "screen_id": "scr-1",
                  "summary_by_connection_type": [
                    {
                      "cache_hit_rate_pct": "87.0",
                      "connection_type": "wifi",
                      "days_reporting": "7",
                      "gb_downloaded": "1.50",
                      "gb_saved_by_cache": "3.00",
                      "total_cache_hits": "200",
                      "total_cache_misses": "30"
                    },
                    {
                      "cache_hit_rate_pct": "83.3",
                      "connection_type": "cellular",
                      "days_reporting": "7",
                      "gb_downloaded": "0.30",
                      "gb_saved_by_cache": "0.60",
                      "total_cache_hits": "50",
                      "total_cache_misses": "10"
                    }
                  ]
                },
                "schema": {
                  "properties": {
                    "daily": {
                      "description": "One row per day and connection type.",
                      "items": {
                        "properties": {
                          "cache_hit_rate_pct": {
                            "description": "Percentage 0-100 to one decimal place, string-encoded\nbecause it is a fixed-point value.\n",
                            "type": "string"
                          },
                          "connection_type": {
                            "type": "string"
                          },
                          "date": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "total_bytes_downloaded": {
                            "description": "String-encoded 64-bit integer.",
                            "type": "string"
                          },
                          "total_bytes_saved_by_cache": {
                            "description": "Ad video plus content video. String-encoded 64-bit integer.",
                            "type": "string"
                          },
                          "total_cache_hits": {
                            "description": "A JSON number here, unlike the byte counters.",
                            "type": "integer"
                          },
                          "total_cache_misses": {
                            "description": "A JSON number here, unlike the byte counters.",
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "period": {
                      "properties": {
                        "end": {
                          "type": "string"
                        },
                        "start": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "screen_id": {
                      "description": "Echo of the requested screen.",
                      "type": "string"
                    },
                    "summary_by_connection_type": {
                      "description": "Whole-period totals per connection type, ordered by\ngigabytes downloaded descending. Every numeric field here is\na string.\n",
                      "items": {
                        "properties": {
                          "cache_hit_rate_pct": {
                            "type": "string"
                          },
                          "connection_type": {
                            "type": "string"
                          },
                          "days_reporting": {
                            "description": "Distinct days with any activity.",
                            "type": "string"
                          },
                          "gb_downloaded": {
                            "type": "string"
                          },
                          "gb_saved_by_cache": {
                            "type": "string"
                          },
                          "total_cache_hits": {
                            "type": "string"
                          },
                          "total_cache_misses": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Usage history for the screen."
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "missing_required_field",
                    "doc_url": "https://api.trillboards.com/docs/errors#missing_required_field",
                    "message": "screen_id is required.",
                    "param": "screen_id",
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "invalid_request_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "`screen_id` is missing."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "code": "SCREEN_ACCESS_DENIED",
                  "message": "You do not have access to this screen",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The authenticated caller does not have access to the `screen_id` given.\nScreen ownership is bound by requireScreenOwnership, so a valid session\ncannot read another owner's screen.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "internal_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#internal_error",
                    "message": "An internal error occurred. Please contact support with request_id req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The query failed, or a supplied date could not be parsed."
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "service_unavailable",
                    "doc_url": "https://api.trillboards.com/docs/errors#service_unavailable",
                    "message": "PostgreSQL is temporarily unavailable.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The PostgreSQL backend is unavailable. Retry with backoff."
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get per-screen bandwidth usage",
        "tags": [
          "Bandwidth Telemetry"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/bid": {
      "post": {
        "description": "Accepts an OpenRTB 2.6 bid request and returns matching bid responses.\nSupports DOOH (via `dooh` object) and web (via `site`/`app` objects).\nMaximum 10 impressions per request. Returns 204 No Content when no matching ads are available.\n",
        "operationId": "processBidRequest",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BidRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BidResponse"
                }
              }
            },
            "description": "Bid response with matching ads"
          },
          "204": {
            "description": "No bid -- no matching ads available"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "example": "Invalid bid request: missing id or imp",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Invalid bid request"
          }
        },
        "security": [
          {
            "exchangeKeyAuth": []
          },
          {
            "exchangeBearerAuth": []
          }
        ],
        "summary": "Process OpenRTB bid request",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/command-ack": {
      "post": {
        "description": "Screens POST here to acknowledge receipt and execution status of a\nremote command pushed to them (refresh, sleep, restart, profile\nupdate). Used by the fleet-management subsystem to mark commands\ncomplete in the audit log.\n",
        "operationId": "acknowledgeRemoteCommand",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "command_id": {
                    "format": "uuid",
                    "type": "string"
                  },
                  "detail": {
                    "description": "Optional human-readable detail (failure reason, etc.)",
                    "type": "string"
                  },
                  "screen_id": {
                    "type": "string"
                  },
                  "status": {
                    "enum": [
                      "ack",
                      "success",
                      "failed",
                      "expired"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "command_id",
                  "screen_id",
                  "status"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "recorded": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Acknowledgement recorded"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Missing required fields"
          }
        },
        "security": [],
        "summary": "Acknowledge a remote command issued to a screen",
        "tags": [
          "Fleet Management"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/complete": {
      "post": {
        "description": "Ad-completion beacon for custom players — integrations that render VAST\nthemselves instead of using the IMA SDK. It is the completion counterpart\nto the impression beacon on the same auction.\n\n## This endpoint settles revenue\n\nOn a qualifying call it publishes a `completed_impressions` row with\ningestion path `vast_event`. Four independent gates must all pass:\n\n- the invalid-traffic check passed (`givt.passed`);\n- the creative is not self-promotional inventory;\n- the demand provider resolves to something other than `unknown`;\n- an idempotency claim on\n  `(screenId, requestId, adId, impressionId, provider)` was won by this\n  call — a second call for the same tuple writes nothing.\n\nThe publish is fire-and-forget: the `200` is returned regardless, and a\nfailure in the billing pipeline is logged, not surfaced. **A `200` here\ndoes not confirm that a billable row landed.**\n\nIt differs from `POST /openrtb/v1/proof-of-play` in two ways that matter:\nthis endpoint carries no price, no play id and no signature, and unlike\nproof-of-play it **is** idempotent, so a retry is safe.\n\n## Rate-limit key trap\n\nThe limiter buckets on `device_id` (snake_case) read from the body,\nfalling back to client IP. The handler itself reads `deviceId`\n(camelCase). A caller that sends only `deviceId` is therefore rate-limited\n**by IP**, which on a shared NAT is a single 300/min bucket for the whole\nfleet. Send `device_id` as well if you want per-device budgeting.\n\n## Two different 200 bodies\n\nThe deduplicated response carries `givt.flags` and **no** `impressionId`;\nthe recorded response carries `impressionId` and no `givt`. Do not assume\neither field is present.\n",
        "operationId": "trackCompletion",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "adId": "ad123",
                "duration": 30,
                "impressionId": "imp456",
                "screenId": "aabbccddeeff00112233aabb"
              },
              "schema": {
                "properties": {
                  "adId": {
                    "description": "Advertisement id. Maximum 256 characters.",
                    "example": "ad123",
                    "maxLength": 256,
                    "type": "string"
                  },
                  "ad_source": {
                    "description": "Also accepted as `adSource`, `source`, `src`. A\nself-promotion label suppresses the billable row.\n",
                    "type": "string"
                  },
                  "ad_type": {
                    "description": "Also accepted as `adType`. Self-promotion labels are matched\ncase- and separator-insensitively, so `self_promo`,\n`self-promo`, and `SelfPromo` are equivalent.\n",
                    "type": "string"
                  },
                  "creativeId": {
                    "description": "Also accepted as `creative_id`. A leading `ima_` prefix is\nstripped before storage.\n",
                    "example": "ima_abc",
                    "type": "string"
                  },
                  "deviceId": {
                    "description": "Device identifier used by the handler. Falls back to the\nclient IP. See the rate-limit key trap above — the limiter\nreads `device_id`, not this field.\n",
                    "type": "string"
                  },
                  "device_id": {
                    "description": "Rate-limit bucket key. Read by the limiter only; the handler\nignores it.\n",
                    "type": "string"
                  },
                  "duration": {
                    "description": "Seconds played. Kept only when finite and within `0..3600`;\nan out-of-range value is silently dropped rather than\nrejected.\n",
                    "example": 30,
                    "format": "double",
                    "type": "number"
                  },
                  "impressionId": {
                    "description": "Impression id from the serve. Optional — when omitted, a\ndeterministic id of the form\n`custom_<screenId>_<adId>_<hourBucket>` is derived, which\ncollapses every completion for that screen and ad within the\nsame hour into one deduplication bucket. Send a real id if\nyou play the same creative more than once an hour.\n",
                    "example": "imp456",
                    "type": "string"
                  },
                  "screenId": {
                    "description": "Screen the ad played on. **Strictly validated** — must be\nexactly 24 hexadecimal characters, unlike every other beacon\non this surface.\n",
                    "example": "aabbccddeeff00112233aabb",
                    "pattern": "^[a-fA-F0-9]{24}$",
                    "type": "string"
                  }
                },
                "required": [
                  "screenId",
                  "adId"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "impressionId": "imp456",
                  "message": "Ad completion tracked successfully",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "givt": {
                      "description": "Present on the deduplicated response only.\n",
                      "properties": {
                        "flags": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "impressionId": {
                      "description": "Present on the recorded response only.",
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Completion accepted — either recorded, or recognised as a duplicate\nof an earlier completion for the same hour bucket. The two cases have\ndifferent bodies; read `message`.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid or missing screenId (must be 24-char hex)"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`screenId` is absent or is not 24 hex characters, or `adId` is absent\nor longer than 256 characters. The two cases have distinct messages.\n"
          },
          "429": {
            "$ref": "#/components/responses/ProgrammaticEventRateLimited"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal server error"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error."
          }
        },
        "security": [],
        "summary": "Track completion event",
        "tags": [
          "Event Tracking"
        ],
        "x-codeSamples": [
          {
            "label": "cURL",
            "lang": "curl",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v1/complete?sig=$SIG\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n        \"screenId\": \"507f1f77bcf86cd799439022\",\n        \"adId\": \"64f000000000000000000001\",\n        \"impressionId\": \"imp456\"\n      }'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-blocklist": {
      "get": {
        "description": "Creative ids a player must refuse to render. Poll this and drop any\nmatching creative from a VAST response before it reaches the screen.\n\nCalled without `screen_id` it returns the **global** blocklist — every\ncreative reviewed and marked blocked platform-wide. Called with a\n`screen_id` it returns the global list unioned with the creatives that\nviolate that screen's own content preferences, deduplicated. Read\n`personalized` to know which of the two you received.\n\nThe global list is cached for five minutes, so a newly blocked creative\ncan take that long to appear.\n",
        "operationId": "getCreativeBlocklist",
        "parameters": [
          {
            "description": "Screen to personalise the list for. Omit for the global list. An\nunknown id is a `404`, not an empty list.\n",
            "example": "69e2e204282d72f900211ed5",
            "in": "query",
            "name": "screen_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "blocked_creative_ids": [
                    "5f2a1b3c4d5e6f7a8b9c0d1e",
                    "6a1b2c3d4e5f60718293a4b5"
                  ],
                  "count": 2,
                  "personalized": true
                },
                "schema": {
                  "properties": {
                    "blocked_creative_ids": {
                      "description": "Flat list of creative ids to refuse.",
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "count": {
                      "description": "Length of `blocked_creative_ids`.",
                      "type": "integer"
                    },
                    "personalized": {
                      "description": "`true` when a `screen_id` was supplied and the screen's own\ncontent preferences were applied on top of the global list.\n",
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Blocklist for the requested scope."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "code": "SCREEN_ACCESS_DENIED",
                  "message": "You do not have access to this screen",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The authenticated caller does not have access to the `screen_id` given.\nScreen ownership is bound by requireScreenOwnership, so a valid session\ncannot read another owner's screen.\n"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "screen_not_found",
                    "doc_url": "https://api.trillboards.com/docs/errors#screen_not_found",
                    "help": {
                      "try_next": "/api/discover?q=screen"
                    },
                    "message": "Screen not found or access denied.",
                    "param": "screen_id",
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "invalid_request_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "No screen matches `screen_id`."
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get creative blocklist",
        "tags": [
          "VAST Telemetry"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-history/{screenId}": {
      "get": {
        "operationId": "getCreativeHistory",
        "parameters": [
          {
            "$ref": "#/components/parameters/ScreenIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "history": [
                    {
                      "ad_data": {
                        "ad_system": "GAM",
                        "advertiser_name": "Acme",
                        "duration": "30",
                        "media_url": "https://cdn.trillboards.com/ads/promo.mp4"
                      },
                      "completed": true,
                      "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                      "requested_at": "2026-08-30T14:12:03.118Z",
                      "review": {
                        "analysis": {
                          "subjects": [
                            "car"
                          ]
                        },
                        "categorization": {
                          "brand": "Acme",
                          "category": "auto"
                        },
                        "content_classification": {
                          "age_rating": "G",
                          "classification_confidence": 0.95,
                          "classification_reasons": [],
                          "contains_adult_content": false,
                          "contains_alcohol": false,
                          "contains_gambling": false,
                          "contains_political": false,
                          "contains_profanity": false,
                          "contains_religious": false,
                          "contains_tobacco": false,
                          "contains_violence": false,
                          "is_known_brand": true
                        },
                        "status": "approved",
                        "thumbnail_url": "https://cdn.trillboards.com/review/thumb.jpg"
                      },
                      "variant_name": "gam_v1"
                    }
                  ],
                  "pagination": {
                    "limit": 50,
                    "page": 1,
                    "pages": 1,
                    "total": 1
                  },
                  "period": {
                    "days": 7,
                    "end": "2026-08-31T09:00:00.000Z",
                    "start": "2026-08-24T09:00:00.000Z"
                  },
                  "source": "pg"
                },
                "schema": {
                  "properties": {
                    "history": {
                      "items": {
                        "properties": {
                          "ad_data": {
                            "properties": {
                              "ad_system": {
                                "type": "string"
                              },
                              "advertiser_name": {
                                "type": "string"
                              },
                              "duration": {
                                "type": "string"
                              },
                              "media_url": {
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "completed": {
                            "type": "boolean"
                          },
                          "creative_id": {
                            "type": "string"
                          },
                          "requested_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "review": {
                            "description": "`null` when the creative was never classified.",
                            "nullable": true,
                            "properties": {
                              "analysis": {
                                "type": "object"
                              },
                              "categorization": {
                                "type": "object"
                              },
                              "content_classification": {
                                "properties": {
                                  "age_rating": {
                                    "type": "string"
                                  },
                                  "classification_confidence": {
                                    "description": "Model confidence in `0..1`. `0` when the\nclassifier stored none — not a confident\nzero.\n",
                                    "format": "double",
                                    "type": "number"
                                  },
                                  "classification_reasons": {
                                    "items": {
                                      "type": "string"
                                    },
                                    "type": "array"
                                  },
                                  "contains_adult_content": {
                                    "type": "boolean"
                                  },
                                  "contains_alcohol": {
                                    "type": "boolean"
                                  },
                                  "contains_gambling": {
                                    "type": "boolean"
                                  },
                                  "contains_political": {
                                    "type": "boolean"
                                  },
                                  "contains_profanity": {
                                    "type": "boolean"
                                  },
                                  "contains_religious": {
                                    "type": "boolean"
                                  },
                                  "contains_tobacco": {
                                    "type": "boolean"
                                  },
                                  "contains_violence": {
                                    "type": "boolean"
                                  },
                                  "is_known_brand": {
                                    "type": "boolean"
                                  }
                                },
                                "type": "object"
                              },
                              "status": {
                                "type": "string"
                              },
                              "thumbnail_url": {
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "variant_name": {
                            "description": "Which serving variant produced the play.",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "properties": {
                        "limit": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        },
                        "pages": {
                          "description": "Total number of pages, not a page size.",
                          "type": "integer"
                        },
                        "total": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "period": {
                      "properties": {
                        "days": {
                          "type": "integer"
                        },
                        "end": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "start": {
                          "format": "date-time",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "source": {
                      "description": "Backend that served the read.",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "What played on this screen, newest first, with the content review\nattached to each creative.\n\n`review` is `null` for a creative that has never been classified —\nthat is \"not reviewed\", not \"reviewed and found clean\". Branch on\n`null` before reading any classification flag.\n\nThis is one of the few paginated operations on this surface, and it\nuses page numbers rather than a cursor: `pagination.pages` is the\npage count, not a page size.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get creative history for a screen",
        "tags": [
          "Creative Review"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-intelligence/experiments": {
      "get": {
        "operationId": "listCreativeExperiments",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "experiments": [
                    {
                      "_id": "68b1c2d3e4f5a60718293a4b",
                      "createdAt": "2026-08-12T11:20:03.771Z",
                      "creative_variants": [
                        {
                          "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                          "variant_id": "fifteen",
                          "weight": 1
                        },
                        {
                          "creative_id": "6a1b2c3d4e5f60718293a4b5",
                          "variant_id": "thirty",
                          "weight": 1
                        }
                      ],
                      "description": null,
                      "ended_at": null,
                      "metric": "completion_rate",
                      "name": "Q3 holiday spot — 15s vs 30s",
                      "partner_id": "65a1b2c3d4e5f6a7b8c9d0e1",
                      "started_at": "2026-08-12T11:20:03.771Z",
                      "status": "running",
                      "traffic_allocation": 1,
                      "updatedAt": "2026-08-12T11:20:03.771Z",
                      "winner_confidence": null,
                      "winner_variant": null
                    }
                  ],
                  "limit": 20,
                  "object": "list",
                  "page": 1,
                  "pages": 1,
                  "total": 1
                },
                "schema": {
                  "properties": {
                    "experiments": {
                      "items": {
                        "properties": {
                          "_id": {
                            "type": "string"
                          },
                          "createdAt": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "creative_variants": {
                            "items": {
                              "properties": {
                                "creative_id": {
                                  "type": "string"
                                },
                                "variant_id": {
                                  "type": "string"
                                },
                                "weight": {
                                  "type": "number"
                                }
                              },
                              "type": "object"
                            },
                            "type": "array"
                          },
                          "description": {
                            "nullable": true,
                            "type": "string"
                          },
                          "ended_at": {
                            "format": "date-time",
                            "nullable": true,
                            "type": "string"
                          },
                          "metric": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "partner_id": {
                            "type": "string"
                          },
                          "started_at": {
                            "format": "date-time",
                            "nullable": true,
                            "type": "string"
                          },
                          "status": {
                            "enum": [
                              "running",
                              "paused",
                              "completed"
                            ],
                            "type": "string"
                          },
                          "traffic_allocation": {
                            "format": "double",
                            "type": "number"
                          },
                          "updatedAt": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "winner_confidence": {
                            "format": "double",
                            "nullable": true,
                            "type": "number"
                          },
                          "winner_variant": {
                            "nullable": true,
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "limit": {
                      "description": "Defaults to `20`.",
                      "type": "integer"
                    },
                    "object": {
                      "enum": [
                        "list"
                      ],
                      "type": "string"
                    },
                    "page": {
                      "description": "Defaults to `1`.",
                      "type": "integer"
                    },
                    "pages": {
                      "description": "Total page count.",
                      "type": "integer"
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "A page of experiments, newest first.\n\n**A backend failure is indistinguishable from an empty list here.**\nWhen the query fails, this operation still answers `200` with\n`experiments: []`, `total: 0`, `page: 1`, `limit: 20`, `pages: 0` —\nthe same body a genuinely empty account produces. Do not treat an\nempty page as proof no experiments exist.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "List creative experiments",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      },
      "post": {
        "description": "Starts an A/B test across two or more creatives. The experiment begins\n`running` immediately — there is no separate start call.\n\nThe response is **`201`**, and its `experiment` is a three-field\nacknowledgement, not the full record. Read the experiment back with\n`GET /openrtb/v1/creative-intelligence/experiments/{id}` if you need\nthe variants, allocation, or metric.\n",
        "operationId": "createCreativeExperiment",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "metric": "completion_rate",
                "name": "Q3 holiday spot — 15s vs 30s",
                "variants": [
                  {
                    "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                    "variant_id": "fifteen",
                    "weight": 1
                  },
                  {
                    "creative_id": "6a1b2c3d4e5f60718293a4b5",
                    "variant_id": "thirty",
                    "weight": 1
                  }
                ]
              },
              "schema": {
                "properties": {
                  "description": {
                    "type": "string"
                  },
                  "metric": {
                    "description": "Decision metric. Governs which statistical test\n`POST .../analyze` runs — see that operation.\n",
                    "type": "string"
                  },
                  "name": {
                    "description": "Experiment label. Required.",
                    "type": "string"
                  },
                  "partner_id": {
                    "description": "Defaults to the authenticated user when omitted.\n",
                    "type": "string"
                  },
                  "traffic_allocation": {
                    "description": "Share of eligible traffic entering the test. Defaults to `1.0`.",
                    "type": "number"
                  },
                  "variants": {
                    "description": "At least two variants, each with a `creative_id`. Fewer than\ntwo, or any variant missing `creative_id`, is a `400`.\n",
                    "items": {
                      "properties": {
                        "creative_id": {
                          "type": "string"
                        },
                        "variant_id": {
                          "description": "Defaults to `variant_<index>` when omitted.\n",
                          "type": "string"
                        },
                        "weight": {
                          "description": "Relative serving weight. Defaults to `1`.",
                          "type": "number"
                        }
                      },
                      "required": [
                        "creative_id"
                      ],
                      "type": "object"
                    },
                    "minItems": 2,
                    "type": "array"
                  }
                },
                "required": [
                  "name",
                  "variants"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "experiment": {
                    "_id": "68b1c2d3e4f5a60718293a4b",
                    "name": "Q3 holiday spot — 15s vs 30s",
                    "status": "running"
                  },
                  "object": "creative_experiment"
                },
                "schema": {
                  "properties": {
                    "experiment": {
                      "description": "Creation acknowledgement — exactly these three fields.\nIt is NOT the record shape returned by the list and get\noperations.\n",
                      "properties": {
                        "_id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "status": {
                          "enum": [
                            "running"
                          ],
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "object": {
                      "enum": [
                        "creative_experiment"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Experiment created and already `running`.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "At least 2 variants are required"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`name` missing, fewer than two variants, or a variant without a\n`creative_id`.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Create creative experiment",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-intelligence/experiments/{id}": {
      "get": {
        "operationId": "getCreativeExperiment",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExperimentIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "experiment": {
                    "_id": "68b1c2d3e4f5a60718293a4b",
                    "createdAt": "2026-08-12T11:20:03.771Z",
                    "creative_variants": [
                      {
                        "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                        "variant_id": "fifteen",
                        "weight": 1
                      },
                      {
                        "creative_id": "6a1b2c3d4e5f60718293a4b5",
                        "variant_id": "thirty",
                        "weight": 1
                      }
                    ],
                    "description": null,
                    "ended_at": null,
                    "metric": "completion_rate",
                    "name": "Q3 holiday spot — 15s vs 30s",
                    "partner_id": "65a1b2c3d4e5f6a7b8c9d0e1",
                    "started_at": "2026-08-12T11:20:03.771Z",
                    "status": "running",
                    "traffic_allocation": 1,
                    "updatedAt": "2026-08-12T11:20:03.771Z",
                    "variant_results": [
                      {
                        "avg_score": 0.7412,
                        "completion_rate": 0.9148,
                        "completions": 18402,
                        "impressions": 20117,
                        "interactions": 96,
                        "qr_scans": 12,
                        "variant": "fifteen"
                      },
                      {
                        "avg_score": 0.7015,
                        "completion_rate": 0.8618,
                        "completions": 17102,
                        "impressions": 19844,
                        "interactions": 141,
                        "qr_scans": 19,
                        "variant": "thirty"
                      }
                    ],
                    "winner_confidence": null,
                    "winner_variant": null
                  },
                  "object": "creative_experiment"
                },
                "schema": {
                  "properties": {
                    "experiment": {
                      "properties": {
                        "_id": {
                          "type": "string"
                        },
                        "createdAt": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "creative_variants": {
                          "items": {
                            "properties": {
                              "creative_id": {
                                "type": "string"
                              },
                              "variant_id": {
                                "type": "string"
                              },
                              "weight": {
                                "type": "number"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "description": {
                          "nullable": true,
                          "type": "string"
                        },
                        "ended_at": {
                          "format": "date-time",
                          "nullable": true,
                          "type": "string"
                        },
                        "metric": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "partner_id": {
                          "type": "string"
                        },
                        "started_at": {
                          "format": "date-time",
                          "nullable": true,
                          "type": "string"
                        },
                        "status": {
                          "enum": [
                            "running",
                            "paused",
                            "completed"
                          ],
                          "type": "string"
                        },
                        "traffic_allocation": {
                          "format": "double",
                          "type": "number"
                        },
                        "updatedAt": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "variant_results": {
                          "items": {
                            "properties": {
                              "avg_score": {
                                "format": "double",
                                "type": "number"
                              },
                              "completion_rate": {
                                "format": "double",
                                "type": "number"
                              },
                              "completions": {
                                "type": "integer"
                              },
                              "impressions": {
                                "type": "integer"
                              },
                              "interactions": {
                                "type": "integer"
                              },
                              "qr_scans": {
                                "type": "integer"
                              },
                              "variant": {
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "winner_confidence": {
                          "format": "double",
                          "nullable": true,
                          "type": "number"
                        },
                        "winner_variant": {
                          "nullable": true,
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "object": {
                      "enum": [
                        "creative_experiment"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "One experiment, plus per-variant results.\n\n`variant_results` is present **only on this operation** — the list\nand update operations return the experiment record without it. It\nis `[]` when the experiment has no plays yet, and also `[]` when\nthe results query failed, which this response does not distinguish.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get creative experiment",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      },
      "patch": {
        "operationId": "updateCreativeExperiment",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExperimentIdPath"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "action": "conclude",
                "winner_variant": "fifteen"
              },
              "schema": {
                "properties": {
                  "action": {
                    "description": "Transitions are checked: `pause` needs `running`, `resume`\nneeds `paused`, `conclude` needs either. A legal action\nagainst the wrong current state is a `409`, not a `400`.\n",
                    "enum": [
                      "pause",
                      "resume",
                      "conclude"
                    ],
                    "type": "string"
                  },
                  "winner_variant": {
                    "description": "Read only when `action` is `conclude`.",
                    "type": "string"
                  }
                },
                "required": [
                  "action"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "experiment": {
                    "_id": "68b1c2d3e4f5a60718293a4b",
                    "createdAt": "2026-08-12T11:20:03.771Z",
                    "creative_variants": [
                      {
                        "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                        "variant_id": "fifteen",
                        "weight": 1
                      },
                      {
                        "creative_id": "6a1b2c3d4e5f60718293a4b5",
                        "variant_id": "thirty",
                        "weight": 1
                      }
                    ],
                    "description": null,
                    "ended_at": "2026-08-31T09:41:00.004Z",
                    "metric": "completion_rate",
                    "name": "Q3 holiday spot — 15s vs 30s",
                    "partner_id": "65a1b2c3d4e5f6a7b8c9d0e1",
                    "started_at": "2026-08-12T11:20:03.771Z",
                    "status": "completed",
                    "traffic_allocation": 1,
                    "updatedAt": "2026-08-12T11:20:03.771Z",
                    "winner_confidence": 0.9812,
                    "winner_variant": "fifteen"
                  },
                  "object": "creative_experiment"
                },
                "schema": {
                  "properties": {
                    "experiment": {
                      "properties": {
                        "_id": {
                          "type": "string"
                        },
                        "createdAt": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "creative_variants": {
                          "items": {
                            "properties": {
                              "creative_id": {
                                "type": "string"
                              },
                              "variant_id": {
                                "type": "string"
                              },
                              "weight": {
                                "type": "number"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "description": {
                          "nullable": true,
                          "type": "string"
                        },
                        "ended_at": {
                          "format": "date-time",
                          "nullable": true,
                          "type": "string"
                        },
                        "metric": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "partner_id": {
                          "type": "string"
                        },
                        "started_at": {
                          "format": "date-time",
                          "nullable": true,
                          "type": "string"
                        },
                        "status": {
                          "enum": [
                            "running",
                            "paused",
                            "completed"
                          ],
                          "type": "string"
                        },
                        "traffic_allocation": {
                          "format": "double",
                          "type": "number"
                        },
                        "updatedAt": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "winner_confidence": {
                          "format": "double",
                          "nullable": true,
                          "type": "number"
                        },
                        "winner_variant": {
                          "nullable": true,
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "object": {
                      "enum": [
                        "creative_experiment"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The updated experiment record.\n\n**No `variant_results` here**, unlike\n`GET /openrtb/v1/creative-intelligence/experiments/{id}` — re-read\nthe experiment if you need them.\n\n`conclude` is idempotent: concluding an already-completed\nexperiment returns the existing record rather than erroring.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Cannot pause experiment in 'paused' state. Allowed from: running"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The action is not legal from the experiment's current state — for\nexample pausing an experiment that is already `paused`.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Pause, resume, or conclude creative experiment",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-intelligence/experiments/{id}/analyze": {
      "post": {
        "operationId": "analyzeCreativeExperiment",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExperimentIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Experiment analysis returned"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Run statistical analysis for experiment",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-intelligence/insights/{creativeId}": {
      "get": {
        "operationId": "getCreativeInsights",
        "parameters": [
          {
            "$ref": "#/components/parameters/CreativeIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Creative insights returned"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get creative insights",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-intelligence/performance/{creativeId}": {
      "get": {
        "operationId": "getCreativePerformance",
        "parameters": [
          {
            "$ref": "#/components/parameters/CreativeIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                  "data": [
                    {
                      "avg_completion_rate": 0.9096,
                      "avg_dwell": 4.62,
                      "avg_score": 0.7412,
                      "total_completions": 38104,
                      "total_impressions": 41892,
                      "total_interactions": 311,
                      "total_qr_scans": 44,
                      "window_count": 1180
                    }
                  ],
                  "object": "creative_performance"
                },
                "schema": {
                  "properties": {
                    "creative_id": {
                      "type": "string"
                    },
                    "data": {
                      "items": {
                        "properties": {
                          "avg_completion_rate": {
                            "description": "Fraction in `0..1`, four decimal places.",
                            "format": "double",
                            "type": "number"
                          },
                          "avg_dwell": {
                            "description": "Seconds, two decimal places.",
                            "format": "double",
                            "type": "number"
                          },
                          "avg_score": {
                            "description": "Composite performance score in `0..1`. A relative\nordering signal, not a revenue or conversion\nestimate.\n",
                            "format": "double",
                            "type": "number"
                          },
                          "period": {
                            "description": "Present only when grouping by day or hour.",
                            "type": "string"
                          },
                          "screen_id": {
                            "description": "Present only when grouping by screen.",
                            "type": "string"
                          },
                          "total_completions": {
                            "type": "integer"
                          },
                          "total_impressions": {
                            "type": "integer"
                          },
                          "total_interactions": {
                            "type": "integer"
                          },
                          "total_qr_scans": {
                            "type": "integer"
                          },
                          "window_count": {
                            "description": "Measurement windows behind the row. Low values make\nevery average above unreliable.\n",
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "object": {
                      "enum": [
                        "creative_performance"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Aggregated delivery and engagement for one creative.\n\n**The array is the whole payload and it can legitimately be empty.**\nAn unknown creative, a creative with no plays, and a backend that\nwas unavailable all return `200` with `data: []`. This operation\nnever distinguishes them, so an empty list is not evidence the\ncreative does not exist.\n\nThe row keys depend on `groupBy`: `screen_id` appears only when\ngrouping by screen, `period` only when grouping by day or hour, and\nan ungrouped request returns a single row with neither.\n\nThis payload also carries a windowed pre-aggregate attention column\nthat the platform is retiring under its primitive-signal contract.\nIt still works, it is deliberately not listed here, and a new\nintegration should not be built on it.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get creative performance metrics",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-intelligence/top": {
      "get": {
        "operationId": "getTopCreatives",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "count": 2,
                  "creatives": [
                    {
                      "avg_completion_rate": 0.9096,
                      "avg_score": 0.7412,
                      "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                      "screen_count": 118,
                      "total_completions": 38104,
                      "total_impressions": 41892,
                      "total_interactions": 311,
                      "total_qr_scans": 44
                    },
                    {
                      "avg_completion_rate": 0.865,
                      "avg_score": 0.6688,
                      "creative_id": "6a1b2c3d4e5f60718293a4b5",
                      "screen_count": 74,
                      "total_completions": 17402,
                      "total_impressions": 20117,
                      "total_interactions": 96,
                      "total_qr_scans": 12
                    }
                  ],
                  "object": "creative_leaderboard"
                },
                "schema": {
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "creatives": {
                      "items": {
                        "properties": {
                          "avg_completion_rate": {
                            "format": "double",
                            "type": "number"
                          },
                          "avg_score": {
                            "description": "Ranking key. Ordering signal only.",
                            "format": "double",
                            "type": "number"
                          },
                          "creative_id": {
                            "type": "string"
                          },
                          "screen_count": {
                            "description": "Distinct screens the creative ran on.",
                            "type": "integer"
                          },
                          "total_completions": {
                            "type": "integer"
                          },
                          "total_impressions": {
                            "type": "integer"
                          },
                          "total_interactions": {
                            "type": "integer"
                          },
                          "total_qr_scans": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "object": {
                      "enum": [
                        "creative_leaderboard"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Leaderboard of creatives by composite score. `count` is the length\nof `creatives`, so an empty leaderboard is `count: 0` with a `200`\n— again not distinguishable from a backend that was unavailable.\n\nThis payload also carries a windowed pre-aggregate attention column\nthat the platform is retiring under its primitive-signal contract.\nIt still works, it is deliberately not listed here, and a new\nintegration should not be built on it.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get top-performing creatives",
        "tags": [
          "Creative Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-review": {
      "get": {
        "description": "Paginated list of creative safety reviews — the records behind\n`GET /openrtb/v1/creative-blocklist`. Each row carries the automated\ncontent classification, the review status, and the manual-review trail.\n\nRequires a bearer JWT whose `role` claim is `admin`. Note the unusual\nstatus mapping on the way in: a missing token is `401`, an **invalid**\ntoken is `403` (not `401`), and a valid non-admin token is also `403`\nwith a different body shape.\n\n## Pagination is unguarded\n\n`page` and `limit` are parsed but neither is clamped nor NaN-checked. A\nnon-numeric `page` produces a `500`. `limit` has no upper bound, so a\nlarge value will attempt to materialise that many rows. `limit=0`\nreturns no rows and reports `pagination.pages` as JSON `null`, because\nthe computed value is infinite and does not survive serialisation.\n\n`low_confidence` is a strict tri-state: exactly `true` or exactly\n`false` filter; **anything else, including `1`, is silently ignored** and\nyou get the unfiltered list.\n\nRows are ordered by `last_seen_at` descending. Note that individual\nrecords mix naming conventions — the id is `_id`, and timestamps are\n`createdAt`/`updatedAt` among otherwise snake_case fields.\n",
        "operationId": "listCreativeReviews",
        "parameters": [
          {
            "description": "Review state. Not validated against the enum — an unrecognised value\nsimply matches no rows rather than erroring.\n",
            "example": "approved",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "pending",
                "approved",
                "flagged",
                "blocked",
                "metadata_only"
              ],
              "type": "string"
            }
          },
          {
            "description": "Ad system that served the creative.",
            "example": "GAM",
            "in": "query",
            "name": "ad_system",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Restrict to classifications the model was unsure about. Strict\ntri-state — see the note above.\n",
            "in": "query",
            "name": "low_confidence",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          },
          {
            "description": "1-based page number. Defaults to 1. A non-numeric value is a `500`.",
            "example": 1,
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "type": "integer"
            }
          },
          {
            "description": "Rows per page. Defaults to 50, with no upper bound enforced. See the\nnote about `limit=0`.\n",
            "example": 50,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "creatives": [
                    {
                      "_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                      "ad_system": "GAM",
                      "analysis": {},
                      "categorization": {},
                      "content_classification": {
                        "age_rating": "G",
                        "classification_confidence": 0.9,
                        "classification_reasons": [],
                        "contains_adult_content": false,
                        "contains_alcohol": false,
                        "contains_gambling": false,
                        "contains_political": false,
                        "contains_profanity": false,
                        "contains_religious": false,
                        "contains_tobacco": false,
                        "contains_violence": false,
                        "is_known_brand": false
                      },
                      "creative_id": "cid_1",
                      "gemini_model": "gemini-3.1-pro-preview",
                      "last_seen_at": "2026-09-01T00:12:44.000Z",
                      "low_confidence": false,
                      "manually_reviewed": false,
                      "manually_reviewed_by": null,
                      "status": "approved",
                      "total_plays": 100
                    }
                  ],
                  "pagination": {
                    "limit": 50,
                    "page": 1,
                    "pages": 1,
                    "total": 1
                  },
                  "source": "pg"
                },
                "schema": {
                  "properties": {
                    "creatives": {
                      "items": {
                        "properties": {
                          "_id": {
                            "description": "Review record id.",
                            "format": "uuid",
                            "type": "string"
                          },
                          "ad_system": {
                            "type": "string"
                          },
                          "advertiser_name": {
                            "type": "string"
                          },
                          "analysis": {
                            "description": "Empty object when the creative has none.",
                            "type": "object"
                          },
                          "blocked_at": {
                            "format": "date-time",
                            "nullable": true,
                            "type": "string"
                          },
                          "blocked_reason": {
                            "nullable": true,
                            "type": "string"
                          },
                          "categorization": {
                            "description": "Empty object when the creative has none.",
                            "type": "object"
                          },
                          "content_classification": {
                            "description": "Automated content-safety classification.",
                            "properties": {
                              "age_rating": {
                                "enum": [
                                  "G",
                                  "PG",
                                  "PG-13",
                                  "R",
                                  "NC-17"
                                ],
                                "type": "string"
                              },
                              "classification_confidence": {
                                "description": "Model confidence, 0-1.",
                                "format": "double",
                                "type": "number"
                              },
                              "classification_reasons": {
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              "contains_adult_content": {
                                "type": "boolean"
                              },
                              "contains_alcohol": {
                                "type": "boolean"
                              },
                              "contains_gambling": {
                                "type": "boolean"
                              },
                              "contains_political": {
                                "type": "boolean"
                              },
                              "contains_profanity": {
                                "type": "boolean"
                              },
                              "contains_religious": {
                                "type": "boolean"
                              },
                              "contains_tobacco": {
                                "type": "boolean"
                              },
                              "contains_violence": {
                                "type": "boolean"
                              },
                              "is_known_brand": {
                                "type": "boolean"
                              }
                            },
                            "type": "object"
                          },
                          "content_type": {
                            "type": "string"
                          },
                          "createdAt": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "creative_id": {
                            "type": "string"
                          },
                          "creative_landing_url": {
                            "type": "string"
                          },
                          "first_seen_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "gemini_model": {
                            "description": "Classifier build that produced the review.",
                            "type": "string"
                          },
                          "last_seen_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "low_confidence": {
                            "type": "boolean"
                          },
                          "manually_reviewed": {
                            "type": "boolean"
                          },
                          "manually_reviewed_by": {
                            "nullable": true,
                            "type": "string"
                          },
                          "media_url": {
                            "type": "string"
                          },
                          "reviewed_at": {
                            "format": "date-time",
                            "nullable": true,
                            "type": "string"
                          },
                          "status": {
                            "enum": [
                              "pending",
                              "approved",
                              "flagged",
                              "blocked",
                              "metadata_only"
                            ],
                            "type": "string"
                          },
                          "thumbnail_url": {
                            "type": "string"
                          },
                          "total_plays": {
                            "type": "integer"
                          },
                          "updatedAt": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "properties": {
                        "limit": {
                          "type": "integer"
                        },
                        "page": {
                          "type": "integer"
                        },
                        "pages": {
                          "description": "`null` when `limit` was 0.",
                          "nullable": true,
                          "type": "integer"
                        },
                        "total": {
                          "description": "Total matching rows across all pages.",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "source": {
                      "description": "Always the literal `pg`.",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "One page of creative reviews."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Admin access required",
                  "statusCode": 403
                },
                "schema": {
                  "properties": {
                    "code": {
                      "enum": [
                        "INVALID_TOKEN"
                      ],
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "statusCode": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Either the bearer token was structurally invalid (`INVALID_TOKEN`,\nwhich arrives as 403 rather than 401) or the token authenticated a\nnon-admin. The two cases have **different body shapes** — the first\nis `{ message, code }`, the second `{ statusCode, message }` with no\n`code` at all.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "internal_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#internal_error",
                    "message": "An internal error occurred. Please contact support with request_id req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The query failed — including when `page` or `limit` could not be\nparsed as a number.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "List creative reviews",
        "tags": [
          "Creative Review"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-review/stats": {
      "get": {
        "description": "Whole-corpus counts for the creative review queue, plus the ten busiest\nad systems. Takes no parameters — it always reports on every review\nrecord, with no date window.\n\nRequires the same admin bearer JWT as `GET /openrtb/v1/creative-review`,\nand the same unusual status mapping applies (an invalid token is `403`,\nnot `401`).\n\n## The status buckets do not sum to `total`\n\n`total` counts every review record, but only five of the six review\nstates have a bucket: rows in the `metadata_only` state are inside\n`total` and inside no bucket. Compute a residual rather than assuming\n`pending + approved + flagged + blocked` accounts for everything.\n\nIn `top_ad_systems` the ad system name is keyed **`_id`**, not\n`ad_system`. The list is capped at ten entries and ordered by count\ndescending, so it is not a complete inventory of ad systems.\n",
        "operationId": "getCreativeReviewStats",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "source": "pg",
                  "stats": {
                    "approved": 30,
                    "blocked": 5,
                    "flagged": 10,
                    "known_brands": 15,
                    "low_confidence_unreviewed": 8,
                    "manually_reviewed_count": 3,
                    "pending": 5,
                    "total": 50,
                    "total_plays": 10000
                  },
                  "top_ad_systems": [
                    {
                      "_id": "GAM",
                      "count": 30
                    },
                    {
                      "_id": "Prebid",
                      "count": 20
                    }
                  ]
                },
                "schema": {
                  "properties": {
                    "source": {
                      "description": "Always the literal `pg`.",
                      "type": "string"
                    },
                    "stats": {
                      "properties": {
                        "approved": {
                          "type": "integer"
                        },
                        "blocked": {
                          "type": "integer"
                        },
                        "flagged": {
                          "type": "integer"
                        },
                        "known_brands": {
                          "type": "integer"
                        },
                        "low_confidence_unreviewed": {
                          "description": "Low-confidence classifications nobody has manually\nreviewed yet — the actionable backlog.\n",
                          "type": "integer"
                        },
                        "manually_reviewed_count": {
                          "type": "integer"
                        },
                        "pending": {
                          "type": "integer"
                        },
                        "total": {
                          "description": "Every review record. See the note about `metadata_only`.",
                          "type": "integer"
                        },
                        "total_plays": {
                          "description": "Summed plays across every reviewed creative.",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "top_ad_systems": {
                      "description": "Ten busiest ad systems, count descending.",
                      "items": {
                        "properties": {
                          "_id": {
                            "description": "Ad system name. Keyed `_id`, not `ad_system`.",
                            "type": "string"
                          },
                          "count": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Corpus-wide review statistics."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Admin access required",
                  "statusCode": 403
                },
                "schema": {
                  "properties": {
                    "code": {
                      "enum": [
                        "INVALID_TOKEN"
                      ],
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "statusCode": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Structurally invalid bearer token (`INVALID_TOKEN`, returned as 403)\nor a non-admin caller. The two cases have different body shapes — see\n`GET /openrtb/v1/creative-review`.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "internal_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#internal_error",
                    "message": "An internal error occurred. Please contact support with request_id req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The query failed."
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get creative review statistics",
        "tags": [
          "Creative Review"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-review/{creativeId}/block": {
      "post": {
        "operationId": "blockCreative",
        "parameters": [
          {
            "$ref": "#/components/parameters/CreativeIdPath"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "reason": "Depicts a competitor brand"
              },
              "schema": {
                "properties": {
                  "reason": {
                    "description": "Free text stored with the block. Defaults to\n`Blocked by admin` when omitted; it is not validated and\nnot an enum.\n",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                  "status": "blocked",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "creative_id": {
                      "type": "string"
                    },
                    "status": {
                      "enum": [
                        "blocked"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Creative blocked. `status` is the literal `blocked`; it echoes the\naction rather than re-reading stored state.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Block creative",
        "tags": [
          "Creative Review"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-review/{creativeId}/confirm": {
      "post": {
        "operationId": "confirmCreativeReview",
        "parameters": [
          {
            "$ref": "#/components/parameters/CreativeIdPath"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "reason": "Explicit content the classifier missed",
                "reviewed_by": "reviewer@example.com",
                "status_override": "blocked"
              },
              "schema": {
                "properties": {
                  "reason": {
                    "type": "string"
                  },
                  "reviewed_by": {
                    "description": "Who is confirming. **Required** — omitting it is a `400`.\n",
                    "type": "string"
                  },
                  "status_override": {
                    "description": "Replaces the AI classification. Any other value is a `400`.\nWhen omitted, the stored status is kept (falling back to\n`pending` if the creative has no review row yet).\n",
                    "enum": [
                      "approved",
                      "flagged",
                      "blocked"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "reviewed_by"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                  "manually_reviewed": true,
                  "manually_reviewed_by": "reviewer@example.com",
                  "status": "blocked",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "creative_id": {
                      "type": "string"
                    },
                    "manually_reviewed": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    },
                    "manually_reviewed_by": {
                      "type": "string"
                    },
                    "status": {
                      "description": "`status_override` when you sent one, otherwise the stored\nstatus, otherwise `pending`.\n",
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Confirmation recorded. `manually_reviewed` is always `true` here —\nit marks that a human adjudicated the creative, and is what\ndownstream serving checks to stop re-running the classifier.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Confirm or override AI classification",
        "tags": [
          "Creative Review"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/creative-review/{creativeId}/unblock": {
      "post": {
        "operationId": "unblockCreative",
        "parameters": [
          {
            "$ref": "#/components/parameters/CreativeIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                  "status": "approved",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "creative_id": {
                      "type": "string"
                    },
                    "status": {
                      "enum": [
                        "approved"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Creative unblocked. Note the status is `approved`, not `unblocked`\n— unblocking restores the creative to the servable state, and the\nresponse names that state rather than the action. No request body\nis read.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Unblock creative",
        "tags": [
          "Creative Review"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/event": {
      "get": {
        "description": "Track an ad event via GET request. Returns a 1x1 transparent GIF pixel.\nUsed by VAST tracking URLs embedded in creatives. Includes GIVT (General\nInvalid Traffic) filtration with deduplication, screen liveness, and demo detection.\n",
        "operationId": "trackEventGet",
        "parameters": [
          {
            "description": "Advertisement ID",
            "in": "query",
            "name": "adid",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Event type",
            "in": "query",
            "name": "event",
            "required": true,
            "schema": {
              "enum": [
                "start",
                "firstQuartile",
                "midpoint",
                "thirdQuartile",
                "complete",
                "impression",
                "viewable",
                "notviewable",
                "viewundetermined",
                "error",
                "click",
                "skip",
                "mute",
                "unmute",
                "pause",
                "resume",
                "fullscreen",
                "exitFullscreen",
                "close"
              ],
              "type": "string"
            }
          },
          {
            "description": "Impression ID for correlation",
            "in": "query",
            "name": "impid",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Screen ID",
            "in": "query",
            "name": "screenId",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Device ID (partner SDK sends P_* IDs)",
            "in": "query",
            "name": "deviceId",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "IMA error code (when event=error)",
            "in": "query",
            "name": "errorCode",
            "schema": {
              "type": "integer"
            }
          },
          {
            "description": "VAST error code (when event=error)",
            "in": "query",
            "name": "vastErrorCode",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "image/gif": {
                "example": "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
                "schema": {
                  "description": "The bytes are fixed. Base64:\n`R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7`\n",
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "A 43-byte 1x1 transparent GIF, always — this operation never returns\nJSON and never returns a non-200.\n\n**Every outcome looks identical on the wire.** A missing `adid`, a\nmalformed ad id, a rejected attribution signature, a deduplicated\nevent and an unhandled server error all return the same pixel with\nthe same `200`. That is deliberate: these URLs are fired by\n`new Image()` and by VAST tracking nodes, where a JSON body trips\nChrome's Opaque Response Blocking and a non-200 shows as a broken\nasset. The consequence for you is that **the GET beacon cannot be\nused to confirm anything was recorded** — use `POST` on the same\npath when you need a verdict.\n\nSent with `Cache-Control: no-store, no-cache, must-revalidate,\nprivate` so intermediaries cannot collapse repeat fires.\n"
          }
        },
        "security": [],
        "summary": "Track event via GET (pixel)",
        "tags": [
          "Event Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      },
      "post": {
        "description": "Track an ad event via POST request. Returns a JSON acknowledgment.\nIncludes GIVT filtration. Deduplicated events return success with givt flags.\n",
        "operationId": "trackEventPost",
        "parameters": [
          {
            "description": "Advertisement ID",
            "in": "query",
            "name": "adid",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Event type (start, complete, impression, error, etc.)",
            "in": "query",
            "name": "event",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Impression ID",
            "in": "query",
            "name": "impid",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Screen ID",
            "in": "query",
            "name": "screenId",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Device ID",
            "in": "query",
            "name": "deviceId",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "deduplicated": {
                    "summary": "Repeat of an event already counted — not recorded again",
                    "value": {
                      "givt": {
                        "flags": [
                          "duplicate"
                        ]
                      },
                      "message": "Event complete deduplicated",
                      "success": true
                    }
                  },
                  "recorded": {
                    "summary": "Event recorded",
                    "value": {
                      "message": "Event complete tracked successfully",
                      "success": true
                    }
                  },
                  "rejectedSignature": {
                    "summary": "Attribution signature rejected — nothing recorded, still a 200",
                    "value": {
                      "reason": "invalid_attribution_signature",
                      "success": false
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "givt": {
                      "description": "Present on the deduplicated body.",
                      "properties": {
                        "flags": {
                          "items": {
                            "enum": [
                              "duplicate",
                              "screen_offline",
                              "demo_ad"
                            ],
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "message": {
                      "description": "Absent on the rejected-signature body.",
                      "type": "string"
                    },
                    "reason": {
                      "description": "Present only when `success` is `false`.",
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The request was handled. **Three different bodies share this\nstatus, and one of them means the event was rejected** — always\nbranch on `success`, never on the status code.\n\n- **Recorded** — `success: true` with `message: \"Event <event>\n  tracked successfully\"`.\n- **Deduplicated** — `success: true`, `message: \"Event <event>\n  deduplicated\"`, and a `givt.flags` array. The event was\n  recognised as a repeat and **not** recorded again; treat it as\n  already-counted, not as newly counted.\n- **Rejected signature** — `success: false` with\n  `reason: \"invalid_attribution_signature\"` and no `message`.\n  Nothing was recorded. This is an explicit `200`, not an error\n  status, so a client that only checks `res.ok` will silently lose\n  every event it mis-signs.\n\n`message` interpolates the `event` query parameter verbatim, so it\nis a echo of your input rather than a controlled vocabulary — match\non `success` and `reason`, not on the message text.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "example": "Missing required parameters",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Missing required parameters"
          }
        },
        "security": [],
        "summary": "Track event via POST (JSON)",
        "tags": [
          "Event Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/events/cleanup": {
      "delete": {
        "operationId": "cleanupOldEvents",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "deletedCount": 0,
                  "message": "event_tracking retention is automated (PG partition rollover + ClickHouse 90-day TTL) — no manual cleanup needed.",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "deletedCount": {
                      "description": "Always `0`.",
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "**This endpoint does nothing.** It deletes no rows, reads no\ndatabase, and ignores every query parameter. `deletedCount` is the\nliteral `0` on every call.\n\nEvent retention is automated — PostgreSQL partition rollover plus a\n90-day ClickHouse TTL — so there is no manual sweep left to run.\nThe route is kept only so existing callers do not break, and it\nreports that state in `message`. Do not schedule it, and do not\nread `deletedCount: 0` as \"nothing needed cleaning\".\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Delete old events",
        "tags": [
          "Event Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/heartbeat": {
      "post": {
        "description": "Records liveness, device telemetry, the actual resettable platform\nadvertising ID, and typed host-owned audience identities for a\nregistered device/screen.\n\nThis POST is intentionally unauthenticated. Supply either the stable\nregistered device fingerprint or its bound `screenId`; the resolved\nregistered-device relationship is the trust and attribution boundary.\nDo not send a Partner API key or device JWT.\n\n`advertising_identity.id` is the real GAID/AAID (or another supported\nplatform ID), never a screen UUID. `hem_sha256` is an existing\none-pass SHA-256 email digest and is not hashed again. `uid2_token` is\nopaque and case-sensitive; send it exactly as issued with its expiry.\n",
        "operationId": "trackHeartbeat",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeviceHeartbeatBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "emptyMetadata": {
                    "summary": "Heartbeat with no telemetry — short-circuit path",
                    "value": {
                      "deviceUpdated": false,
                      "message": "Heartbeat recorded",
                      "screenId": "507f1f77bcf86cd799439022",
                      "server_time": "2026-08-31T09:20:30.218Z",
                      "socket_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.PLACEHOLDER.PLACEHOLDER",
                      "success": true,
                      "timestamp": "2026-08-31T09:20:30.220Z"
                    }
                  },
                  "typical": {
                    "summary": "Telemetry accepted, one command pending",
                    "value": {
                      "commands": [
                        {
                          "command": "reload",
                          "commandId": "68b1c2d3e4f5a60718293a4b",
                          "id": "68b1c2d3e4f5a60718293a4b",
                          "payload": {},
                          "queued_at": "2026-08-31T09:19:41.006Z"
                        }
                      ],
                      "deviceUpdated": true,
                      "message": "Heartbeat recorded and device updated",
                      "screenId": "507f1f77bcf86cd799439022",
                      "server_time": "2026-08-31T09:20:00.112Z",
                      "socket_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.PLACEHOLDER.PLACEHOLDER",
                      "success": true,
                      "timestamp": "2026-08-31T09:20:00.114Z"
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "capabilityWriteRejectedReason": {
                      "description": "Present only when a reported capability was refused.",
                      "type": "string"
                    },
                    "commands": {
                      "description": "Pending remote commands. Absent when none are queued.",
                      "items": {
                        "properties": {
                          "command": {
                            "type": "string"
                          },
                          "commandId": {
                            "description": "Same value as `id`.",
                            "type": "string"
                          },
                          "id": {
                            "type": "string"
                          },
                          "payload": {
                            "type": "object"
                          },
                          "queued_at": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "deviceInfo": {
                      "description": "Present only when device specs were resolved on this beat.",
                      "properties": {
                        "dimensions": {
                          "description": "Physical screen size, e.g. `55\"×31\"`.",
                          "type": "string"
                        },
                        "model": {
                          "type": "string"
                        },
                        "vendor": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "deviceUpdated": {
                      "type": "boolean"
                    },
                    "mdm_enroll": {
                      "description": "Present only while an enrolment offer is outstanding.",
                      "properties": {
                        "enrollmentCode": {
                          "type": "string"
                        },
                        "orgId": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "message": {
                      "description": "`Heartbeat recorded`, or `Heartbeat recorded and device\nupdated` when device columns changed.\n",
                      "type": "string"
                    },
                    "screenId": {
                      "description": "Authoritative screen binding — adopt this value.",
                      "type": "string"
                    },
                    "sensing_config": {
                      "description": "Present only when a sensing config version is pending\nacknowledgement. Echo `config_version` on the next\nheartbeat to acknowledge it and stop it being resent.\n",
                      "properties": {
                        "config_version": {
                          "type": "string"
                        },
                        "identity": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "server_time": {
                      "description": "Authoritative clock. Prefer it over the device clock when\nscheduling.\n",
                      "format": "date-time",
                      "type": "string"
                    },
                    "socket_token": {
                      "description": "Short-lived realtime-socket token. 5-minute TTL.",
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "timestamp": {
                      "format": "date-time",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Heartbeat recorded.\n\n## Read `screenId` back — it is a correction channel\n\nThe `screenId` in the response is the **authoritative** binding for\nthe device that called, and it may differ from the one you sent. A\ndevice that has been rebound is told so here and is expected to\nadopt the returned value.\n\n## Most of the body is conditional\n\nOnly `success`, `message`, `timestamp`, `server_time`, `screenId`\nand `deviceUpdated` are always present. `socket_token`, `commands`,\n`sensing_config`, `deviceInfo`, `mdm_enroll` and\n`capabilityWriteRejectedReason` each appear only when they apply —\nan absent key is not an empty one.\n\nA heartbeat carrying no telemetry takes a short-circuit path that\npins `deviceUpdated: false` and can never include `deviceInfo`,\n`mdm_enroll` or `capabilityWriteRejectedReason`; it still returns\npending `commands` and a refreshed `socket_token`.\n\n## `socket_token` expires in five minutes\n\nIt is minted per heartbeat with a 5-minute TTL, so the ~30s\nheartbeat cycle is what keeps it alive. It authenticates the\nrealtime socket only. **It is not the device's API credential** and\nmust not be stored where the long-lived device token is read from —\ndoing so makes one missed heartbeat look like a revoked device.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Neither a resolvable fingerprint nor screenId was supplied"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Registered device or screen binding was not found"
          }
        },
        "security": [],
        "summary": "Track registered-screen heartbeat and targeting identities",
        "tags": [
          "Event Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/ima-player/{screenId}": {
      "get": {
        "description": "Returns an HTML page that bootstraps Google's IMA SDK against\nTrillboards' VAST endpoint for the given screen. Used by partners\nwhose displays embed a browser-based player. The page is\nresponsive and adapts to the partner's screen aspect ratio.\n",
        "operationId": "getImaPlayerHarness",
        "parameters": [
          {
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "IMA player HTML"
          }
        },
        "security": [],
        "summary": "HTML5 IMA SDK player harness for a screen",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/impression": {
      "post": {
        "description": "JSON form of the impression beacon. Same handler as\n`GET /openrtb/v1/pixel`, so the ledger writes, the dedup rule, the\nsignature requirement, and the rate limit are all identical — see that\noperation for the full mechanics.\n\nPrefer this form over the pixel whenever the caller can read a response\nbody: every outcome here is machine-readable JSON, whereas the pixel\nreturns an opaque GIF for success, deduplication, signature rejection,\nand server error alike.\n\n**This beacon does not bill.** It records a *started* play. Revenue is\nsettled by `POST /openrtb/v1/proof-of-play`.\n\nTwo shape traps carried over from the shared handler:\n\n- The signature travels in the **query string** (`?sig=`) even on this\n  POST, and it covers query fields only — **the JSON body is entirely\n  unsigned**.\n- The body spells the ad id `adId` (camelCase), but the signature\n  canonicalisation uses `adid` (lowercase). They are different names for\n  the same value in different places.\n\n`mediaType` is accepted by the parser and then discarded; do not rely on\nit.\n",
        "operationId": "recordImpression",
        "parameters": [
          {
            "description": "Attribution signature — see `GET /openrtb/v1/pixel` for the exact\nconstruction. Required on this POST too, and computed over query\nparameters only.\n",
            "example": "4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b",
            "in": "query",
            "name": "sig",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "adId": "ad123",
                "impressionId": "imp456",
                "screenId": "screen789"
              },
              "schema": {
                "properties": {
                  "adId": {
                    "description": "Advertisement id. Must be a JSON **string** — a numeric value\nreaches an unguarded `startsWith` and produces a 500.\n",
                    "example": "ad123",
                    "type": "string"
                  },
                  "adSource": {
                    "description": "Also accepted as `ad_source`, `source`, `src`. Self-promotion\nlabels suppress downstream billing.\n",
                    "type": "string"
                  },
                  "adType": {
                    "description": "Also accepted as `ad_type`.",
                    "type": "string"
                  },
                  "advertiserId": {
                    "description": "Also accepted as `advertiser_id`.",
                    "type": "string"
                  },
                  "allocationId": {
                    "description": "Loop-slot allocation the play was booked against.",
                    "type": "string"
                  },
                  "bidPrice": {
                    "description": "Also accepted as `winPrice` or `win_price`.",
                    "format": "double",
                    "type": "number"
                  },
                  "bid_response_id": {
                    "description": "Also accepted as `bidResponseId`.",
                    "type": "string"
                  },
                  "campaignId": {
                    "description": "Also accepted as `campaign_id`.",
                    "type": "string"
                  },
                  "creative_id": {
                    "description": "Also accepted as `creativeId`. A leading `ima_` prefix is\nstripped before storage.\n",
                    "type": "string"
                  },
                  "currency": {
                    "example": "USD",
                    "type": "string"
                  },
                  "deviceId": {
                    "description": "Also accepted as `device_id`, `fingerprint`,\n`deviceFingerprint`, `device_fingerprint`; falls back to the\nclient IP.\n",
                    "type": "string"
                  },
                  "displayContext": {
                    "description": "Presentation the ad was rendered in.\n",
                    "properties": {
                      "displayAreaRatio": {
                        "example": 1,
                        "format": "double",
                        "type": "number"
                      },
                      "displayMode": {
                        "example": "fullscreen",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "impressionId": {
                    "description": "Impression id minted when the ad was served.",
                    "example": "imp456",
                    "type": "string"
                  },
                  "request_id": {
                    "description": "Auction id; takes precedence over `impressionId` in the dedup\nkey. Also accepted as `requestId`, `bid_request_id`,\n`bidRequestId`, `auction_id`, `auctionId`.\n",
                    "type": "string"
                  },
                  "screenId": {
                    "description": "Screen the ad played on; part of the dedup key.",
                    "example": "screen789",
                    "type": "string"
                  }
                },
                "required": [
                  "adId",
                  "impressionId"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "impressionId": "imp456",
                  "message": "Impression tracked successfully",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "duplicate": {
                      "description": "Present and `true` only on a deduplicated repeat.",
                      "type": "boolean"
                    },
                    "impressionId": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "reason": {
                      "description": "Present only when `success` is `false`. The single value\nemitted here is `invalid_attribution_signature`.\n",
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Impression recorded, or recognised as a duplicate. Both outcomes are\n`200` — read `duplicate` to tell them apart. A rejected signature is\nALSO `200`, with `success: false` and a `reason`; check `success`\nbefore treating the call as delivered.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Missing required parameters: adid, impid"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`adId` or `impressionId` missing, or the advertisement could not be\nresolved or is not `approved`/`active`.\n"
          },
          "429": {
            "$ref": "#/components/responses/TrackingRateLimited"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Impression tracking error",
                  "message": "Cannot read properties of undefined"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error. Note this body echoes the raw internal\nexception message in `message`; do not surface it to end users.\n"
          }
        },
        "security": [],
        "summary": "Record impression",
        "tags": [
          "Impression Tracking"
        ],
        "x-codeSamples": [
          {
            "label": "cURL",
            "lang": "curl",
            "source": "# Records a STARTED play. This endpoint never bills — settle with\n# POST /openrtb/v1/proof-of-play.\ncurl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v1/impression?sig=$SIG\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n        \"screenId\": \"507f1f77bcf86cd799439022\",\n        \"adId\": \"64f000000000000000000001\",\n        \"impressionId\": \"imp456\"\n      }'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/impressions/batch": {
      "post": {
        "operationId": "batchUpdateImpressions",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "impressions": [
                  {
                    "impressionId": "imp_pop_1"
                  },
                  {
                    "impressionId": "imp_pop_0"
                  }
                ]
              },
              "schema": {
                "properties": {
                  "impressions": {
                    "description": "Updates to apply, one per impression. An empty array — or\nanything that is not an array — is a `400`.\n",
                    "items": {
                      "properties": {
                        "impressionId": {
                          "description": "Identifies the row to update, and is the value echoed\nin `results.errors[].impressionId` on failure.\n",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "minItems": 1,
                    "type": "array"
                  }
                },
                "required": [
                  "impressions"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "allSucceeded": {
                    "summary": "Every entry applied",
                    "value": {
                      "results": {
                        "errors": [],
                        "failed": 0,
                        "processed": 2
                      },
                      "success": true
                    }
                  },
                  "partialFailure": {
                    "summary": "One entry failed — still a 200",
                    "value": {
                      "results": {
                        "errors": [
                          {
                            "error": "impression not found",
                            "impressionId": "imp_pop_0"
                          }
                        ],
                        "failed": 1,
                        "processed": 1
                      },
                      "success": true
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "results": {
                      "properties": {
                        "errors": {
                          "description": "One entry per failure. Empty when `failed` is 0.",
                          "items": {
                            "properties": {
                              "error": {
                                "description": "The raised error's message, verbatim.",
                                "type": "string"
                              },
                              "impressionId": {
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "failed": {
                          "description": "Entries that raised.",
                          "type": "integer"
                        },
                        "processed": {
                          "description": "Entries applied successfully.",
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "description": "That the batch was processed, not that every entry\nsucceeded.\n",
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The batch ran. **`200` does not mean every row succeeded** — this\nendpoint is partial-success by design: each entry is applied\nindependently and a failure is collected rather than aborting the\nbatch. Always read `results.failed` and `results.errors`; a batch\nwhere every entry failed still answers `200`.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid impressions data"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`impressions` was missing, not an array, or empty."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Batch update impressions",
        "tags": [
          "Impression Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/inline/{token}": {
      "get": {
        "description": "Inline ad delivery uses a short-lived signed token to avoid leaking\ncreative URLs to off-network actors. Screens redeem the token at this\nendpoint to fetch the inline VAST document; the token is single-use\nand expires after 60 seconds.\n",
        "operationId": "resolveInlineAdToken",
        "parameters": [
          {
            "description": "Signed, single-use, time-limited token.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Inline VAST 4.2 XML document"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Token expired, already redeemed, or never issued"
          }
        },
        "security": [],
        "summary": "Resolve an inline-ad fulfilment token",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/interaction": {
      "post": {
        "description": "Records a single viewer interaction with a playing ad — a touch, a click,\na QR scan. Analytics only; it never affects delivery counts or revenue.\n\nThe durable write is fire-and-forget and the `interactionId` in the\nresponse is minted before it settles, so a `200` with an id is not\nconfirmation that the row persisted.\n\n`interactionType` is **not** validated against an enum — any string is\naccepted and stored verbatim. The values the reporting side actually\nqueries are `qr_scan` and `qr_interaction` for the QR report; `click` and\n`touch` are the other conventional values. Inventing a new value will\nstore cleanly and then be invisible to every existing report.\n\n`metadata.userAgent` and `metadata.ip` are **overwritten** server-side\nfrom the request headers and socket. Anything you send under those two\nkeys is discarded.\n",
        "operationId": "trackInteraction",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "interactionType": "click",
                "metadata": {
                  "adId": "670000000000000000000010",
                  "screenId": "670000000000000000000020"
                },
                "relativeTime": 1500,
                "timestamp": 1711929600000,
                "trackingId": "trk_001"
              },
              "schema": {
                "properties": {
                  "interactionType": {
                    "description": "Interaction kind. Free-form; see the note above about which\nvalues the reports recognise.\n",
                    "example": "click",
                    "type": "string"
                  },
                  "metadata": {
                    "description": "Free-form context. `userAgent` and `ip` are reserved — they\nare replaced with server-observed values.\n",
                    "example": {
                      "adId": "670000000000000000000010",
                      "screenId": "670000000000000000000020"
                    },
                    "type": "object"
                  },
                  "relativeTime": {
                    "description": "Milliseconds since the ad started playing.",
                    "example": 1500,
                    "type": "integer"
                  },
                  "timestamp": {
                    "description": "Epoch milliseconds. Defaults to the server clock when\nomitted.\n",
                    "example": 1711929600000,
                    "format": "int64",
                    "type": "integer"
                  },
                  "trackingId": {
                    "description": "Session key correlating this interaction with the play it\nbelongs to, and with a later\n`POST /openrtb/v1/interaction-summary`.\n",
                    "example": "trk_001",
                    "type": "string"
                  }
                },
                "required": [
                  "trackingId",
                  "interactionType"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "interactionId": "670000000000000000000031",
                  "message": "Interaction tracked successfully",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "interactionId": {
                      "description": "24-character hexadecimal id minted for this interaction.",
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Interaction accepted. `interactionId` is generated before the durable\nwrite completes; a storage failure is logged and does not change this\nstatus.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "message": "trackingId and interactionType are required",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`trackingId` or `interactionType` is missing."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "connection terminated unexpectedly",
                  "message": "Failed to track interaction",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error. `error` echoes the raw internal exception\nmessage; do not surface it to end users.\n"
          }
        },
        "security": [],
        "summary": "Track ad interaction",
        "tags": [
          "Interaction Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/interaction-summary": {
      "post": {
        "description": "Submits a whole interaction session in one call — the batch counterpart\nto `POST /openrtb/v1/interaction`. Analytics only; no billing effect.\n\n## The array items use a different field name\n\nEach element of `interactions` names its kind `type`, **not**\n`interactionType` as the single-interaction endpoint does. An item\nsending `interactionType` stores a null kind and is invisible to every\nreport. This is the most common mistake on this endpoint.\n\nTwo more asymmetries against the single-interaction endpoint: items get\n**no** `timestamp` default, so an item without one stores a null\ntimestamp; and `userAgent`/`ip` are **not** injected here.\n\nThe top-level `metadata` is merged into every item's own metadata and\n**wins on key collisions**, so a session-wide `campaignId` cannot be\noverridden per item.\n\n## The response is an echo, not a receipt\n\n`summary` restates what you sent plus a server timestamp. It contains no\nids and no write confirmation. The batch insert is awaited but its errors\nare swallowed, so a `200` can accompany a failed write. `interactionCount`\nis echoed verbatim and is **never** cross-checked against\n`interactions.length`.\n",
        "operationId": "trackInteractionSummary",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "duration": 5000,
                "interactionCount": 2,
                "interactions": [
                  {
                    "metadata": {
                      "adId": "670000000000000000000010",
                      "screenId": "670000000000000000000020"
                    },
                    "relativeTime": 1500,
                    "timestamp": 1711929600000,
                    "type": "click"
                  },
                  {
                    "metadata": {
                      "adId": "670000000000000000000010"
                    },
                    "relativeTime": 2500,
                    "timestamp": 1711929700000,
                    "type": "qr_scan"
                  }
                ],
                "metadata": {
                  "campaignId": "cmp-1"
                },
                "trackingId": "trk_001"
              },
              "schema": {
                "properties": {
                  "duration": {
                    "description": "Session length in milliseconds. Echoed, never validated.",
                    "example": 5000,
                    "type": "integer"
                  },
                  "interactionCount": {
                    "description": "Caller's own count. Echoed back unchanged and never compared\nwith `interactions.length`.\n",
                    "example": 2,
                    "type": "integer"
                  },
                  "interactions": {
                    "description": "Individual interactions. Written only when this is an array;\nomitting it stores nothing and still returns `200`.\n",
                    "items": {
                      "properties": {
                        "metadata": {
                          "description": "Per-item context. The request's top-level `metadata` is\nmerged in afterwards and wins on collisions.\n",
                          "type": "object"
                        },
                        "relativeTime": {
                          "description": "Milliseconds since the ad started playing.",
                          "example": 1500,
                          "type": "integer"
                        },
                        "timestamp": {
                          "description": "Epoch milliseconds. No server-side default on this\nendpoint.\n",
                          "example": 1711929600000,
                          "format": "int64",
                          "type": "integer"
                        },
                        "type": {
                          "description": "Interaction kind. Note the name — `type` here,\n`interactionType` on the single-interaction endpoint.\n",
                          "example": "click",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "type": "array"
                  },
                  "metadata": {
                    "description": "Session-wide context. Echoed in `summary` AND merged into\nevery item, overriding item keys of the same name.\n",
                    "example": {
                      "campaignId": "cmp-1"
                    },
                    "type": "object"
                  },
                  "trackingId": {
                    "description": "Session key. The only validated field on this endpoint.\n",
                    "example": "trk_001",
                    "type": "string"
                  }
                },
                "required": [
                  "trackingId"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Interaction summary tracked successfully",
                  "success": true,
                  "summary": {
                    "duration": 5000,
                    "interactionCount": 2,
                    "metadata": {
                      "campaignId": "cmp-1"
                    },
                    "timestamp": 1788134400000,
                    "trackingId": "trk_001"
                  }
                },
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "summary": {
                      "properties": {
                        "duration": {
                          "type": "integer"
                        },
                        "interactionCount": {
                          "type": "integer"
                        },
                        "metadata": {
                          "type": "object"
                        },
                        "timestamp": {
                          "description": "Server clock in epoch milliseconds.",
                          "format": "int64",
                          "type": "integer"
                        },
                        "trackingId": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Summary accepted. `summary` is an echo of the request plus a server\ntimestamp — it is not a write receipt, and a failed batch insert\nstill returns this response.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "message": "trackingId is required",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`trackingId` is missing."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "connection terminated unexpectedly",
                  "message": "Failed to track interaction summary",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error. `error` echoes the raw internal exception\nmessage.\n"
          }
        },
        "security": [],
        "summary": "Submit interaction summary",
        "tags": [
          "Interaction Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/omsdk/event": {
      "get": {
        "description": "Beacon-friendly variant of `POST /openrtb/v1/omsdk/event` — the same\nhandler exposed as GET so a VAST player can fire OMSDK verification\nevents from an `<img>` / `navigator.sendBeacon` context where a request\nbody is not available. Event fields are passed as query parameters.\n",
        "operationId": "recordOmsdkEventBeacon",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "recorded": {
                    "summary": "Event recorded, no viewability verdict on this event",
                    "value": {
                      "eventCount": 2,
                      "eventType": "start",
                      "recorded": true,
                      "sessionId": "omsdk_507f1f77bcf86cd799439022_1788134400000",
                      "sessionStatus": "active",
                      "success": true,
                      "viewability": null
                    }
                  },
                  "withViewability": {
                    "summary": "Event that settled an MRC viewability verdict",
                    "value": {
                      "eventCount": 3,
                      "eventType": "impression",
                      "recorded": true,
                      "sessionId": "omsdk_507f1f77bcf86cd799439022_1788134400000",
                      "sessionStatus": "active",
                      "success": true,
                      "viewability": {
                        "durationThreshold": 2000,
                        "meetsDurationThreshold": true,
                        "meetsPixelThreshold": true,
                        "mrcCompliant": true,
                        "pixelThreshold": 0.5,
                        "viewable": true,
                        "viewableDurationMs": 2400,
                        "viewablePercentage": 0.82
                      }
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "eventCount": {
                      "description": "Running total of events on this session.",
                      "type": "integer"
                    },
                    "eventType": {
                      "type": "string"
                    },
                    "recorded": {
                      "type": "boolean"
                    },
                    "sessionId": {
                      "type": "string"
                    },
                    "sessionStatus": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "viewability": {
                      "description": "Present only when this event triggered an MRC viewability\nevaluation. `null` otherwise.\n",
                      "nullable": true,
                      "properties": {
                        "durationThreshold": {
                          "description": "Milliseconds required. `1000` for display, `2000` for\nvideo.\n",
                          "type": "integer"
                        },
                        "meetsDurationThreshold": {
                          "type": "boolean"
                        },
                        "meetsPixelThreshold": {
                          "type": "boolean"
                        },
                        "mrcCompliant": {
                          "type": "boolean"
                        },
                        "pixelThreshold": {
                          "description": "Share of pixels required. `1.0` for display, `0.5`\nfor video.\n",
                          "format": "double",
                          "type": "number"
                        },
                        "viewable": {
                          "type": "boolean"
                        },
                        "viewableDurationMs": {
                          "type": "integer"
                        },
                        "viewablePercentage": {
                          "format": "double",
                          "type": "number"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Event recorded against the session.\n\nThis GET shares its handler with the POST variant, so it returns\n**JSON, not a pixel** — unlike `GET /openrtb/v1/event`. It is fired\nfrom a VAST `<Tracking>` node where a JSON body is acceptable\nbecause no `Image()` load depends on it.\n\n`viewability` is `null` on most events. It is computed only when\nthe arriving event is one that can settle a viewability verdict, so\na `null` here means \"not evaluated on this event\", never \"not\nviewable\".\n\n`eventCount` is the running total for the session, so it increments\nacross calls and is the cheapest way to confirm your events are\nlanding on the session you think they are.\n"
          }
        },
        "security": [],
        "summary": "Record OMSDK event (GET image-beacon variant)",
        "tags": [
          "OMSDK"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      },
      "post": {
        "description": "Records one OMID measurement event, or a batch of them, against a session\nopened by `POST /openrtb/v1/omsdk/session`.\n\n## Single and batch forms behave differently on failure\n\nSend one event as a bare object. Send several by wrapping them in\n`batch`, up to 50 per request.\n\nIn the **single** form, a bad session or a bad event type is an HTTP\nerror: `404` when the session does not exist, `409` when it is already\ncompleted, `400` for an unknown event type or malformed `data`.\n\nIn the **batch** form **none of those statuses can occur**. Per-item\nfailures are collected into an `errors` array under a `200`, so a batch\nwhere every item failed still answers `200` with `processed: 0`. Always\nread `failed` and `errors`; do not rely on the status code. Only two\nthings fail a batch as a whole: exceeding 50 items (`400`) and the\nper-IP rate limit (`429`). The batch path also skips the per-session\nrate limit and does not attach device or user-agent context.\n\nThe `errors` key is **omitted entirely** when nothing failed, rather than\nbeing an empty array.\n\n## Two events close the session\n\n`complete` and `sessionFinish` mark the session completed. Every event\nsent afterwards is rejected with `409`. Order your quartiles accordingly,\nand send `sessionFinish` last.\n\n## Rate limits\n\n120 events per minute per session and 600 per minute per client IP, both\nsliding windows. These are separate from the OpenRTB tracking limiter and\nreturn their own body shape, shown under `429`.\n\nA GET variant of this same operation exists for beacon contexts where no\nrequest body is available.\n",
        "operationId": "recordOmsdkEvent",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "data": {
                  "test": true
                },
                "eventType": "impression",
                "sessionId": "omsdk_m1x2y3z4_a1b2c3d4e5f60718"
              },
              "schema": {
                "description": "Either a single event (`sessionId` + `eventType`) or a `batch`\narray. If `batch` is present and is an array, the single-event\nfields are ignored.\n",
                "properties": {
                  "batch": {
                    "description": "Up to 50 events. Each item is read for `sessionId`,\n`eventType`, and `data` only — any other key on an item is\nignored.\n",
                    "items": {
                      "properties": {
                        "data": {
                          "type": "object"
                        },
                        "eventType": {
                          "type": "string"
                        },
                        "sessionId": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "sessionId",
                        "eventType"
                      ],
                      "type": "object"
                    },
                    "maxItems": 50,
                    "type": "array"
                  },
                  "data": {
                    "description": "Event payload. Must be a plain object — an array is rejected —\nand must serialise to at most 4096 bytes.\n",
                    "type": "object"
                  },
                  "deviceId": {
                    "description": "Merged into `data` after validation. Falls back to the client\nIP. Single form only.\n",
                    "type": "string"
                  },
                  "eventType": {
                    "description": "OMID event. Required in the single form. `complete` and\n`sessionFinish` close the session.\n",
                    "enum": [
                      "sessionStart",
                      "sessionFinish",
                      "loaded",
                      "impression",
                      "geometryChange",
                      "start",
                      "firstQuartile",
                      "midpoint",
                      "thirdQuartile",
                      "complete",
                      "viewable",
                      "notViewable",
                      "viewUndetermined",
                      "verificationNotExecuted",
                      "bufferStart",
                      "bufferFinish",
                      "skipped",
                      "volumeChange",
                      "playerStateChange",
                      "adUserInteraction"
                    ],
                    "example": "impression",
                    "type": "string"
                  },
                  "sessionId": {
                    "description": "Session id from the create call. Required in the single form.",
                    "example": "omsdk_m1x2y3z4_a1b2c3d4e5f60718",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "eventCount": 3,
                  "eventType": "impression",
                  "recorded": true,
                  "sessionId": "omsdk_m1x2y3z4_a1b2c3d4e5f60718",
                  "sessionStatus": "active",
                  "success": true,
                  "viewability": null
                },
                "schema": {
                  "properties": {
                    "errors": {
                      "description": "Batch form only, and **omitted entirely** when nothing\nfailed.\n",
                      "items": {
                        "properties": {
                          "error": {
                            "type": "string"
                          },
                          "eventType": {
                            "nullable": true,
                            "type": "string"
                          },
                          "sessionId": {
                            "nullable": true,
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "eventCount": {
                      "description": "Events recorded on this session so far. Single form only.",
                      "type": "integer"
                    },
                    "eventType": {
                      "description": "Single form only.",
                      "type": "string"
                    },
                    "failed": {
                      "description": "Batch form only. Items rejected.",
                      "type": "integer"
                    },
                    "processed": {
                      "description": "Batch form only. Items accepted.",
                      "type": "integer"
                    },
                    "recorded": {
                      "description": "Single form only.",
                      "type": "boolean"
                    },
                    "results": {
                      "description": "Batch form only.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "sessionId": {
                      "description": "Single form only.",
                      "type": "string"
                    },
                    "sessionStatus": {
                      "description": "Single form only. `completed` after a closing event.",
                      "enum": [
                        "active",
                        "completed"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    },
                    "viewability": {
                      "description": "Single form only, and non-null only for `geometryChange`,\n`viewable`, and `notViewable`.\n",
                      "nullable": true,
                      "properties": {
                        "durationThreshold": {
                          "description": "1000 ms for display, 2000 ms for video.",
                          "type": "integer"
                        },
                        "meetsDurationThreshold": {
                          "type": "boolean"
                        },
                        "meetsPixelThreshold": {
                          "type": "boolean"
                        },
                        "mrcCompliant": {
                          "type": "boolean"
                        },
                        "pixelThreshold": {
                          "description": "100 for display, 50 for video.",
                          "format": "double",
                          "type": "number"
                        },
                        "viewable": {
                          "type": "boolean"
                        },
                        "viewableDurationMs": {
                          "type": "integer"
                        },
                        "viewablePercentage": {
                          "format": "double",
                          "type": "number"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Event recorded (single form), or the batch was processed. The two\nforms return **different body shapes** — the batch response has\n`processed`/`failed`/`results`, the single response has\n`eventType`/`recorded`/`eventCount`.\n\nA batch with failures also lands here. Read `failed` and `errors`.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Missing required parameters: sessionId, eventType"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Single form: `sessionId` or `eventType` missing, an unrecognised\n`eventType` (the message lists every valid value), or `data` that is\nnot a plain object or exceeds 4096 bytes. Batch form: more than 50\nitems.\n"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Session not found",
                  "message": "Session not found: omsdk_m1x2y3z4_a1b2c3d4e5f60718"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "No such session — it never existed or has aged out of the one-hour\nwindow. **Single form only**; in a batch this becomes an `errors`\nentry under a `200`.\n"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Session already completed",
                  "message": "Session omsdk_m1x2y3z4_a1b2c3d4e5f60718 is already completed"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The session is already completed or expired, so it accepts no further\nevents. **Single form only.**\n"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Session rate limit exceeded",
                  "limit": 120,
                  "retry_after_seconds": 60
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "limit": {
                      "description": "The ceiling that was hit.",
                      "type": "integer"
                    },
                    "retry_after_seconds": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Per-session (120/min) or per-IP (600/min) event limit exceeded. This\nbody shape is specific to OMSDK and matches neither the tracking\nlimiter nor the DSP limiter.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Failed to record OMSDK event",
                  "message": "Redis connection closed"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error. `message` echoes the raw internal exception.\n"
          }
        },
        "security": [],
        "summary": "Record OMSDK event",
        "tags": [
          "OMSDK"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/omsdk/report/{campaignId}": {
      "get": {
        "operationId": "getOmsdkVerificationReport",
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "noSessions": {
                    "summary": "No sessions — note the missing keys",
                    "value": {
                      "campaignId": "65f3a1b2c4d5e6f7a8b9c0d1",
                      "generatedAt": "2026-08-31T09:30:00.004Z",
                      "metrics": {},
                      "mrcComplianceRate": 0,
                      "queryPeriod": {
                        "endDate": "now",
                        "startDate": "all"
                      },
                      "totalSessions": 0,
                      "viewabilityRate": 0
                    }
                  },
                  "withSessions": {
                    "summary": "Campaign with measured sessions",
                    "value": {
                      "activeSessions": 12,
                      "campaignId": "65f3a1b2c4d5e6f7a8b9c0d1",
                      "completedSessions": 38104,
                      "expiredSessions": 3776,
                      "generatedAt": "2026-08-31T09:30:00.004Z",
                      "metrics": {
                        "avgViewableDurationMs": 2410,
                        "avgViewablePercentage": 0.82,
                        "firstSession": "2026-08-01T06:02:11.000Z",
                        "lastSession": "2026-08-30T23:58:40.000Z",
                        "mrcCompliantSessions": 33493,
                        "uniqueAds": 26,
                        "uniqueScreens": 118,
                        "viewableSessions": 35240
                      },
                      "mrcComplianceRate": 0.7995,
                      "omidVersion": "1.4",
                      "partner": {
                        "name": "Trillboards",
                        "version": "1.0.0"
                      },
                      "queryPeriod": {
                        "endDate": "now",
                        "startDate": "2026-08-01T00:00:00Z"
                      },
                      "screenBreakdown": [],
                      "totalSessions": 41892,
                      "viewabilityRate": 0.8412
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "activeSessions": {
                      "description": "Absent when `totalSessions` is 0.",
                      "type": "integer"
                    },
                    "campaignId": {
                      "type": "string"
                    },
                    "completedSessions": {
                      "description": "Absent when `totalSessions` is 0.",
                      "type": "integer"
                    },
                    "expiredSessions": {
                      "description": "Sessions that aged out of the one-hour window without\ncompleting. Absent when `totalSessions` is 0.\n",
                      "type": "integer"
                    },
                    "generatedAt": {
                      "format": "date-time",
                      "type": "string"
                    },
                    "metrics": {
                      "description": "Empty object when `totalSessions` is 0.",
                      "properties": {
                        "avgViewableDurationMs": {
                          "type": "integer"
                        },
                        "avgViewablePercentage": {
                          "format": "double",
                          "type": "number"
                        },
                        "firstSession": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "lastSession": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "mrcCompliantSessions": {
                          "type": "integer"
                        },
                        "uniqueAds": {
                          "description": "A count, not a list.",
                          "type": "integer"
                        },
                        "uniqueScreens": {
                          "description": "A count, not a list.",
                          "type": "integer"
                        },
                        "viewableSessions": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "mrcComplianceRate": {
                      "format": "double",
                      "type": "number"
                    },
                    "omidVersion": {
                      "description": "OMID API version these sessions were measured under.",
                      "type": "string"
                    },
                    "partner": {
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "version": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "queryPeriod": {
                      "properties": {
                        "endDate": {
                          "description": "Your filter, or the literal `now`.",
                          "type": "string"
                        },
                        "startDate": {
                          "description": "Your filter, or the literal `all`.",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "screenBreakdown": {
                      "description": "Capped at 100 screens. Best-effort: an empty array can\nmean the breakdown query failed, not that no screens ran.\n",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "totalSessions": {
                      "type": "integer"
                    },
                    "viewabilityRate": {
                      "format": "double",
                      "type": "number"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Campaign-level Open Measurement roll-up.\n\n**A campaign with no sessions returns a short body, not a zeroed\nfull one.** In that case only `campaignId`, `queryPeriod`,\n`totalSessions: 0`, `viewabilityRate: 0`, `mrcComplianceRate: 0`,\nan empty `metrics` object and `generatedAt` are present —\n`completedSessions`, `screenBreakdown`, `omidVersion` and `partner`\nare all absent. Do not index into `metrics` without checking\n`totalSessions` first.\n\n`metrics.uniqueScreens` and `metrics.uniqueAds` are **counts**, not\nlists. `screenBreakdown` is capped at 100 screens and is best-effort\n— it degrades to an empty array rather than failing the request.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get OMSDK verification report",
        "tags": [
          "OMSDK"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/omsdk/session": {
      "post": {
        "description": "Opens an Open Measurement (OMID 1.4) verification session for one ad\nplay. Everything else in the OMSDK flow hangs off the `sessionId` this\nreturns.\n\nThe response is **`201`**, not `200`. It also tells you where to go next:\n`verificationScriptUrl` is the script to load into the player, and\n`eventEndpoint` is where to post events — prefer both over hard-coding\npaths, since the session id is already embedded in the script URL.\n\nSessions live for **one hour** in the hot store. After that, or once a\n`complete` or `sessionFinish` event closes the session, further events\nfor that id are rejected — `404` if the session has aged out entirely,\n`409` if it is present but already completed.\n\n`adSessionType` is **not validated**: any string is accepted and stored.\nIt affects only the viewability thresholds, and the branch is a single\nequality test against `display`, so every other value — including a typo\n— is treated as video (50% of pixels for 2000 ms, against display's 100%\nfor 1000 ms).\n",
        "operationId": "createOmsdkSession",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "adId": "ad_001",
                "adSessionType": "video",
                "displayContext": {
                  "mode": "fullscreen"
                },
                "impressionId": "imp_001",
                "screenId": "screen_001"
              },
              "schema": {
                "properties": {
                  "adId": {
                    "description": "Advertisement being measured. The only required field.",
                    "example": "ad_001",
                    "type": "string"
                  },
                  "adSessionType": {
                    "default": "video",
                    "description": "Measurement profile. Only `display` changes behaviour; see\nthe note above. Defaults to `video`.\n",
                    "enum": [
                      "display",
                      "video",
                      "native"
                    ],
                    "example": "video",
                    "type": "string"
                  },
                  "campaignId": {
                    "type": "string"
                  },
                  "displayContext": {
                    "description": "Presentation the ad renders in. `mode`/`displayMode` and\n`areaRatio`/`displayAreaRatio` are both accepted spellings.\n",
                    "properties": {
                      "areaRatio": {
                        "default": 1,
                        "format": "double",
                        "type": "number"
                      },
                      "lBarPosition": {
                        "nullable": true,
                        "type": "string"
                      },
                      "mode": {
                        "default": "fullscreen",
                        "example": "fullscreen",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "impressionId": {
                    "example": "imp_001",
                    "type": "string"
                  },
                  "screenId": {
                    "example": "screen_001",
                    "type": "string"
                  },
                  "verificationVendors": {
                    "description": "Third-party verification vendors to declare. Defaults to empty.",
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "adId"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "session": {
                    "adSessionContext": {
                      "adId": "ad_001",
                      "displayContext": {
                        "areaRatio": 1,
                        "lBarPosition": null,
                        "mode": "fullscreen"
                      },
                      "impressionId": "imp_001",
                      "screenId": "screen_001"
                    },
                    "adSessionType": "video",
                    "eventEndpoint": "https://api.trillboards.com/openrtb/v1/omsdk/event",
                    "omidApiVersion": "1.4",
                    "partner": {
                      "name": "Trillboards",
                      "version": "1.0.0"
                    },
                    "sessionId": "omsdk_m1x2y3z4_a1b2c3d4e5f60718",
                    "startedAt": "2026-09-01T00:00:00.000Z",
                    "status": "active",
                    "verificationScriptUrl": "https://api.trillboards.com/openrtb/v1/omsdk/verification.js?sid=omsdk_m1x2y3z4_a1b2c3d4e5f60718"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "session": {
                      "properties": {
                        "adSessionContext": {
                          "properties": {
                            "adId": {
                              "type": "string"
                            },
                            "displayContext": {
                              "type": "object"
                            },
                            "impressionId": {
                              "nullable": true,
                              "type": "string"
                            },
                            "screenId": {
                              "nullable": true,
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "adSessionType": {
                          "type": "string"
                        },
                        "eventEndpoint": {
                          "description": "Where to post measurement events.",
                          "format": "uri",
                          "type": "string"
                        },
                        "omidApiVersion": {
                          "description": "OMID API version implemented. Currently `1.4`.",
                          "type": "string"
                        },
                        "partner": {
                          "properties": {
                            "name": {
                              "description": "Always `Trillboards`.",
                              "type": "string"
                            },
                            "version": {
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "sessionId": {
                          "description": "Server-minted id, `omsdk_` followed by a base-36\ntimestamp and 16 hex characters.\n",
                          "type": "string"
                        },
                        "startedAt": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "status": {
                          "description": "`active` on creation.",
                          "type": "string"
                        },
                        "verificationScriptUrl": {
                          "description": "Script to load in the player. The session id is already\nbound into the URL.\n",
                          "format": "uri",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Session created. Retain `session.sessionId` — every subsequent event\nmust carry it.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Missing required parameter: adId"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`adId` is missing."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Failed to create OMSDK session",
                  "message": "Redis connection closed"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Session creation failed. `message` echoes the raw internal exception;\ndo not surface it to end users.\n"
          }
        },
        "security": [],
        "summary": "Create OMSDK session",
        "tags": [
          "OMSDK"
        ],
        "x-codeSamples": [
          {
            "label": "cURL",
            "lang": "curl",
            "source": "# Answers 201, not 200. Use the returned verificationScriptUrl and\n# eventEndpoint rather than rebuilding the paths yourself.\ncurl -sS -X POST https://api.trillboards.com/openrtb/v1/omsdk/session \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n        \"adId\": \"64f000000000000000000001\",\n        \"screenId\": \"507f1f77bcf86cd799439022\",\n        \"adSessionType\": \"video\"\n      }'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/omsdk/session/{sessionId}": {
      "get": {
        "deprecated": true,
        "description": "This debug endpoint previously returned the full Mongo OMSDKSession document including\nthe `events[]` array. It had no production caller (verified via 14-day CloudWatch\nInsights scan, 2026-04-30) and was removed as part of the OMSDK Mongo→ClickHouse\ncutover (Phase 1b PR-2). Use ClickHouse-backed analytics endpoints for session/event\nlookups going forward.\n",
        "operationId": "getOmsdkSessionStatus",
        "parameters": [
          {
            "$ref": "#/components/parameters/SessionIdPath"
          }
        ],
        "responses": {
          "410": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Endpoint deprecated; see response body for migration guidance"
          }
        },
        "security": [],
        "summary": "DEPRECATED — returns 410 Gone",
        "tags": [
          "OMSDK"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/omsdk/verification.js": {
      "get": {
        "description": "Serves the JavaScript measurement agent a VAST player loads to drive an\nOMID session. Do not construct this URL by hand — take it from\n`session.verificationScriptUrl` in the create-session response, which\nalready carries the right `sid`.\n\nOnce loaded, the script fires `sessionStart`, `loaded`, and `impression`\nback to back, then polls geometry once a second, emitting\n`geometryChange`. It closes the session with `sessionFinish` on page\nunload, or after a five-minute inactivity timeout, whichever comes first.\nEvents are posted to `POST /openrtb/v1/omsdk/event` via `sendBeacon`,\nfalling back to `XMLHttpRequest` and then `fetch` — one event is sent\nbare, two or more are wrapped in `batch`.\n\nIt exposes `window.TrillboardsOMSDK` with `reportEvent`,\n`reportGeometry`, `finishSession`, and `getGeometry`, so a player can\ndrive measurement manually rather than relying on the automatic\nlifecycle.\n\n## This endpoint has no error status\n\n**Every response is `200`.** An unknown or expired `sid` is not an error:\nthe script is still served, with an empty session id baked in, and it\nthen silently declines to initialise — so measurement is off and nothing\non the wire says so. If session binding matters, verify it when you\ncreate the session, not here.\n\nA server-side failure is also `200`, returning a small stub that logs an\nerror to the browser console. Distinguish the two by the response\nheaders: the real script sends `Cache-Control: public, max-age=3600` and\nan `X-OMID-Partner` header, while the stub sends `Cache-Control:\nno-cache` and no `X-OMID-Partner`.\n",
        "operationId": "getOmsdkVerificationScript",
        "parameters": [
          {
            "description": "Session id to bind the script to. Optional, and an unknown value is\nnot rejected — see above. Without a resolvable session the script\nloads and does nothing.\n",
            "example": "omsdk_m1x2y3z4_a1b2c3d4e5f60718",
            "in": "query",
            "name": "sid",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/javascript": {
                "example": "(function() {\n  var SESSION_ID = \"omsdk_m1x2y3z4_a1b2c3d4e5f60718\";\n  var EVENT_ENDPOINT = \"https://api.trillboards.com/openrtb/v1/omsdk/event\";\n  var PARTNER_NAME = \"Trillboards\";\n  var PARTNER_VERSION = \"1.0.0\";\n  var OMID_API_VERSION = \"1.4\";\n  /* ... session lifecycle, geometry polling, beacon transport ... */\n  global.TrillboardsOMSDK = { version: PARTNER_VERSION, omidApiVersion: OMID_API_VERSION, sessionId: SESSION_ID, reportEvent: reportEvent, reportGeometry: reportGeometry, finishSession: finishSession, getGeometry: getGeometry };\n})();\n",
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "The verification script. Also the status for an unresolvable `sid`\nand for a server-side failure — check the headers to tell the real\nscript from the error stub.\n",
            "headers": {
              "Cache-Control": {
                "description": "`public, max-age=3600` for the real script; `no-cache` for the\nerror stub.\n",
                "schema": {
                  "example": "public, max-age=3600",
                  "type": "string"
                }
              },
              "X-OMID-Partner": {
                "description": "Partner name and version. **Absent on the error stub** — its\npresence is how you tell a real script from a stub.\n",
                "schema": {
                  "example": "Trillboards/1.0.0",
                  "type": "string"
                }
              }
            }
          }
        },
        "security": [],
        "summary": "Get OMSDK verification script",
        "tags": [
          "OMSDK"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/owned-direct/play-token/{screenId}": {
      "post": {
        "description": "Mints a fresh, replay-safe lifecycle map for one currently servable\nSchedule-mode direct placement. The device token and path both bind the\ncaller to `screenId`; the server revalidates the `(adId, screenId)`\nplacement and rejects self-promo before signing anything.\n\nEvery returned URL contains the same server-issued, HMAC-bound\n`request_id`. Fire the returned lifecycle URLs verbatim. A client\nimpression ID may be supplied for diagnostics, but it is not the\nbilling or replay identity. Responses are private and non-cacheable.\n",
        "operationId": "mintOwnedDirectPlayToken",
        "parameters": [
          {
            "description": "MongoDB ObjectId of the screen bound to the device token.",
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-fA-F]{24}$",
              "type": "string"
            }
          },
          {
            "description": "Current Unix time in milliseconds for replay protection.",
            "in": "header",
            "name": "X-Device-Timestamp",
            "required": true,
            "schema": {
              "pattern": "^\\d{10,16}$",
              "type": "string"
            }
          },
          {
            "description": "Unique nonce for this authenticated mint request.",
            "in": "header",
            "name": "X-Device-Nonce",
            "required": true,
            "schema": {
              "maxLength": 160,
              "minLength": 8,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "adId": {
                    "description": "Advertisement ObjectId for the direct placement.",
                    "pattern": "^[0-9a-fA-F]{24}$",
                    "type": "string"
                  },
                  "impressionId": {
                    "description": "Optional client correlation value; never used as the signed replay identity.",
                    "maxLength": 256,
                    "type": "string"
                  },
                  "screenId": {
                    "description": "Optional body echo; the authenticated path value is authoritative.",
                    "pattern": "^[0-9a-fA-F]{24}$",
                    "type": "string"
                  }
                },
                "required": [
                  "adId"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "complete_url": {
                      "format": "uri",
                      "type": "string"
                    },
                    "event_urls": {
                      "properties": {
                        "complete": {
                          "format": "uri",
                          "type": "string"
                        },
                        "error": {
                          "format": "uri",
                          "type": "string"
                        },
                        "firstQuartile": {
                          "format": "uri",
                          "type": "string"
                        },
                        "impression": {
                          "format": "uri",
                          "type": "string"
                        },
                        "midpoint": {
                          "format": "uri",
                          "type": "string"
                        },
                        "start": {
                          "format": "uri",
                          "type": "string"
                        },
                        "thirdQuartile": {
                          "format": "uri",
                          "type": "string"
                        }
                      },
                      "required": [
                        "impression",
                        "start",
                        "firstQuartile",
                        "midpoint",
                        "thirdQuartile",
                        "complete",
                        "error"
                      ],
                      "type": "object"
                    },
                    "request_id": {
                      "pattern": "^req_\\d{10,}_[A-Za-z0-9]+$",
                      "type": "string"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "request_id",
                    "complete_url",
                    "event_urls"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Fresh signed lifecycle map for exactly one direct play.",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store, private`.",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              }
            },
            "description": "Invalid screen/ad identity or missing replay-guard input."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              }
            },
            "description": "Device token is missing, invalid, or no longer registered."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              }
            },
            "description": "Device token is not bound to the requested screen."
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              }
            },
            "description": "Placement is not currently servable or is self-promo."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitError"
                }
              }
            },
            "description": "Per-screen OpenRTB tracking rate limit exceeded."
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              }
            },
            "description": "Placement revalidation or token mint failed unexpectedly."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LegacyError"
                }
              }
            },
            "description": "Attribution signing is unavailable; the mint fails closed."
          }
        },
        "security": [
          {
            "deviceTokenAuth": []
          }
        ],
        "summary": "Mint signed lifecycle URLs for one Schedule direct play",
        "tags": [
          "Event Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/performance": {
      "post": {
        "description": "Player and device health telemetry for a screen. Diagnostic only — it\nnever touches delivery counts, impressions, or revenue.\n\nBoth writes are fire-and-forget, so a `200` means the payload was\naccepted and dispatched, not that it was durably stored.\n\nOnly the seven metric keys enumerated below are read; any other key\ninside `metrics` is discarded silently. Values are copied through with no\nvalidation or unit coercion, so send them in the units documented here\nor the series will be uncomparable across fleets.\n\nUnlike `POST /openrtb/v1/complete`, `screenId` is only checked for\ntruthiness — there is no 24-hex validation and no rate limit on this\nendpoint.\n",
        "operationId": "trackPerformance",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "adId": "ima_perf_async",
                "metrics": {
                  "fps": 60,
                  "loadTime": 10,
                  "renderTime": 5
                },
                "screenId": "69a076ad2ccbba67a5ba1c1e"
              },
              "schema": {
                "properties": {
                  "adId": {
                    "description": "Advertisement the sample was taken during, when the sample is\nad-scoped. Never validated.\n",
                    "example": "ima_perf_async",
                    "type": "string"
                  },
                  "metrics": {
                    "description": "Metric bag. Only these seven keys are persisted; everything\nelse is dropped.\n",
                    "properties": {
                      "cpuUsage": {
                        "description": "CPU utilisation as a percentage, 0-100.",
                        "format": "double",
                        "type": "number"
                      },
                      "errorCount": {
                        "description": "Errors observed in the sample window. The only key with a\ndefault — absent becomes `0`.\n",
                        "example": 0,
                        "type": "integer"
                      },
                      "fps": {
                        "description": "Frames per second observed during playback.",
                        "example": 60,
                        "format": "double",
                        "type": "number"
                      },
                      "loadTime": {
                        "description": "Milliseconds from request to asset availability.",
                        "example": 10,
                        "format": "double",
                        "type": "number"
                      },
                      "memoryUsage": {
                        "description": "Resident memory in megabytes.",
                        "format": "double",
                        "type": "number"
                      },
                      "networkLatency": {
                        "description": "Round-trip time in milliseconds.",
                        "format": "double",
                        "type": "number"
                      },
                      "renderTime": {
                        "description": "Milliseconds from asset availability to first frame.",
                        "example": 5,
                        "format": "double",
                        "type": "number"
                      }
                    },
                    "type": "object"
                  },
                  "screenId": {
                    "description": "Screen the telemetry describes. Presence-checked only.",
                    "example": "69a076ad2ccbba67a5ba1c1e",
                    "type": "string"
                  }
                },
                "required": [
                  "screenId",
                  "metrics"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Performance metrics recorded",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Telemetry accepted. Also returned when the durable write fails — the\nwrites are fire-and-forget.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Missing required parameters"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`screenId` or `metrics` is missing. The message does not say which.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal server error"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error."
          }
        },
        "security": [],
        "summary": "Track performance telemetry",
        "tags": [
          "Event Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/pixel": {
      "get": {
        "description": "Impression beacon fired from an `<img>` tag or a VAST `<Impression>`\nnode. Shares one handler with `POST /openrtb/v1/impression`; the two\ndiffer only in how parameters arrive and in what a failure looks like on\nthe wire.\n\n## This beacon does not bill\n\nIt writes `impression_ledger` and an `impression_events` row with\n`status: \"started\"`, and increments delivery counters. It never writes\n`completed_impressions`. Revenue is settled by\n`POST /openrtb/v1/proof-of-play`; a fired pixel is evidence of a\n*started* play, not of a paid one.\n\n## The response is not always an image\n\nSuccess, a deduplicated repeat, a rejected signature, and an unhandled\nexception all return the same 42-byte transparent GIF with `200`. But a\nmissing-parameter or invalid-advertisement failure returns **JSON with a\n`400`**, and the rate limiter returns **JSON with a `429`** — so an\n`<img>` consumer sees a broken image with no way to read the reason.\nIntegrations that need to observe failures must use the POST form.\n\n## Signature is required\n\n`?sig=` is mandatory whenever the `VAST_ATTRIBUTION_REQUIRE_SIG` runtime\nflag is on, which is the production default. It is a truncated HMAC —\nsee the `sig` parameter for the exact construction, and note that it\ncovers a **fixed nine-field subset** of the query string. `impid`,\n`creative_id`, `campaign_id`, and `bid_price` are NOT signed, so\ntampering with them does not invalidate the signature.\n\n## Deduplication\n\nBest-effort, via a Redis `SET NX EX 3600` on\n`impression:dedup:{request_id or impid}:{screenId or \"none\"}`. Three\nconsequences worth designing around: the key uses `request_id` in\npreference to `impid`; `adid` is **not** part of the key; and when Redis\nis unreachable dedup is skipped entirely, while a Redis call slower than\n50 ms is treated as a duplicate and the impression is silently dropped.\n",
        "operationId": "trackImpressionPixel",
        "parameters": [
          {
            "description": "Advertisement id. Lowercase `adid` on this GET form — the POST form\nspells the same field `adId`, and the signature canonicalisation\nuses the lowercase spelling for both.\n",
            "example": "ima_req_1777972097640_4b9ffbc2c7dd",
            "in": "query",
            "name": "adid",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Impression id minted when the ad was served.",
            "example": "trb_pt_1777972102723_g7v6xn79",
            "in": "query",
            "name": "impid",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Attribution signature. HMAC-SHA256 over the canonical string, hex,\n**truncated to the first 32 characters**.\n\nThe canonical string is these nine fields, in this exact order, each\nas `key=encodeURIComponent(value)` joined by `&`, with an absent\nvalue signing as empty:\n\n`adid`, `deviceId`, `event`, `provider`, `request_id`, `screenId`,\n`src`, `var`, `variant_name`\n\nAny other query parameter — and, on the POST form, the entire JSON\nbody — is outside the signature. Requests are rejected when the\nsigning secret is unset or shorter than 32 characters, so a\nmisconfigured environment rejects every beacon rather than accepting\nunsigned ones.\n",
            "example": "4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b",
            "in": "query",
            "name": "sig",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-f]{32}$",
              "type": "string"
            }
          },
          {
            "description": "Screen the ad played on. Part of the dedup key when present.",
            "example": "69e2e204282d72f900211ed5",
            "in": "query",
            "name": "screenId",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Auction/bid-request id. Takes precedence over `impid` in the dedup\nkey. Also accepted as `requestId`, `bid_request_id`, `bidRequestId`,\n`auction_id`, or `auctionId`.\n",
            "example": "req_1777972097640_4b9ffbc2c7dd",
            "in": "query",
            "name": "request_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Loop-slot allocation the play was booked against. camelCase only on\nthis operation — `allocation_id` is not read here.\n",
            "in": "query",
            "name": "allocationId",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Device identifier. Falls back through `device_id`, `fingerprint`,\n`deviceFingerprint`, `device_fingerprint`, then the `Referer` query\nstring, then the client IP.\n",
            "in": "query",
            "name": "deviceId",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Creative id. A leading `ima_` prefix is stripped server-side before\nstorage, so `ima_abc` and `abc` resolve to the same creative. Also\naccepted as `creativeId`.\n",
            "in": "query",
            "name": "creative_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Also accepted as `campaignId`.",
            "in": "query",
            "name": "campaign_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Also accepted as `advertiserId`.",
            "in": "query",
            "name": "advertiser_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Winning bid price. Coerced with `Number()`; a non-finite value is\nstored as null rather than rejected. Also accepted as `bidPrice`,\n`win_price`, or `winPrice`.\n",
            "in": "query",
            "name": "bid_price",
            "required": false,
            "schema": {
              "format": "double",
              "type": "number"
            }
          },
          {
            "example": "USD",
            "in": "query",
            "name": "currency",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Demand source label. Also accepted as `adSource`, `source`, or `src`.\nSelf-promotion labels here suppress downstream billing.\n",
            "example": "vast_wrap_passthrough",
            "in": "query",
            "name": "ad_source",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Also accepted as `adType`.",
            "in": "query",
            "name": "ad_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Also accepted as `bidResponseId`.",
            "in": "query",
            "name": "bid_response_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "**Carve-out.** `type=error` is checked before everything else — even\nbefore the signature gate — and delegates the whole request to the\nerror-event handler. It is not an impression at all. Leave this\nparameter off unless you are firing a VAST error beacon.\n",
            "in": "query",
            "name": "type",
            "required": false,
            "schema": {
              "enum": [
                "error"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "image/gif": {
                "example": "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "A 42-byte transparent GIF, returned identically for a recorded\nimpression, a deduplicated repeat, a rejected signature, and an\nunhandled server error. **The image carries no outcome** — a `200`\nGIF is not confirmation that the impression was stored.\n",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store, no-cache, must-revalidate, private`.",
                "schema": {
                  "example": "no-store, no-cache, must-revalidate, private",
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Missing required parameters: adid, impid"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    },
                    "reason": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`adid` or `impid` missing, or the advertisement could not be resolved\nor is not in an `approved`/`active` state. **JSON, not an image** —\nan `<img>` consumer sees only a broken image here.\n"
          },
          "429": {
            "$ref": "#/components/responses/TrackingRateLimited"
          }
        },
        "security": [],
        "summary": "Track impression via pixel",
        "tags": [
          "Impression Tracking"
        ],
        "x-codeSamples": [
          {
            "label": "cURL",
            "lang": "curl",
            "source": "# `impid` is mandatory alongside `adid` — without it the request\n# is rejected with `Missing required parameters: adid, impid`,\n# however valid the signature is. It is NOT one of the signed\n# parameters, so adding it does not change $SIG.\ncurl -sS -G https://api.trillboards.com/openrtb/v1/pixel \\\n  -d adid=64f000000000000000000001 \\\n  -d impid=trb_pt_1777972102723_g7v6xn79 \\\n  -d screenId=507f1f77bcf86cd799439022 \\\n  -d event=impression \\\n  -d request_id=req-pop-1 \\\n  -d \"sig=$SIG\"\n"
          },
          {
            "label": "Node.js — sign the beacon",
            "lang": "javascript",
            "source": "const crypto = require('crypto');\n\n// The signed subset is FIXED and already alphabetical. Every key is\n// emitted even when you have no value for it, so a stripped\n// parameter changes the signature rather than disappearing from it.\nconst SIGNED_PARAMS = [\n  'adid', 'deviceId', 'event', 'provider',\n  'request_id', 'screenId', 'src', 'var', 'variant_name',\n];\n\nfunction attributionSig(params, secret) {\n  // Secrets shorter than 32 characters are rejected server-side,\n  // and the request is refused rather than accepted unsigned.\n  if (!secret || secret.length < 32) {\n    throw new Error('VAST_ATTRIBUTION_HMAC_SECRET must be >= 32 chars');\n  }\n  const canonical = SIGNED_PARAMS\n    .map((k) => `${k}=${encodeURIComponent(params[k] ?? '')}`)\n    .join('&');\n  return crypto\n    .createHmac('sha256', secret)\n    .update(canonical)\n    .digest('hex')\n    .slice(0, 32); // truncated to 16 bytes\n}\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/proof-of-play": {
      "post": {
        "description": "MRC-aligned DOOH proof-of-play, fired by the screen application when an\nad finishes or is cut short.\n\n## This is the billing endpoint\n\nOf every beacon on this surface, this is the one that settles revenue.\nOn a qualifying call it publishes a `completed_impressions` row and\nfires the advertiser's billing URL (`burl`). Both are gated on ALL of:\n\n- the play completed — `completionType` is `full` or `complete`;\n- the invalid-traffic check passed (`givt.passed`);\n- the creative is not self-promotional inventory;\n- the demand provider resolves to something other than `unknown`\n  (an unresolved provider is dropped silently, still returning `200`).\n\n`POST /openrtb/v1/impression` and `GET /openrtb/v1/pixel` record a\n*started* play and never bill. `GET /openrtb/v1/win` validates a win and\nnever bills.\n\n## There is no deduplication\n\nUnlike the impression beacon, this endpoint has no dedup key. Every call\nmints a fresh `playId` and can publish a fresh billable row. **A blind\nretry double-bills.** Retry only on a transport failure where you have\nno response at all, and prefer making the play itself idempotent\nupstream.\n\n## The returned signature is minted by us, not verified from you\n\n`signature` in the response is a receipt Trillboards generates so a\nbuyer can verify the play independently. Nothing in the request carries\nan Ed25519 signature, and none is checked. The only credential you send\nis the HMAC `?sig=` query parameter.\n\nThe signed payload is six dot-joined fields in this exact order:\n\n```\n<signatureVersion>.<timestampMs>.<adId>.<impressionId>.<screenId>.<deviceId>\n```\n\nwhere `timestampMs` is `playStartTime` in epoch **milliseconds**, and\n`impressionId` falls back to the generated `playId` when you omit it.\n`completionType`, the durations, `givt`, `channel`, and `displayContext`\nare NOT covered. Verifiers must accept both prefixes: `ed25519=` (v2,\nthe normal path) and `sha256=` (v1, the fallback when no Ed25519 signing\nkey is configured).\n\n## Server-authoritative fields\n\n`impressionMultiplier` is resolved server-side from the audience-quantity\naudit for the auction and fails closed to `1`. A client-supplied value is\nread from the body and then discarded — sending it changes nothing.\n",
        "operationId": "recordProofOfPlay",
        "parameters": [
          {
            "description": "Attribution signature — construction is identical to\n`GET /openrtb/v1/pixel`. Computed over query parameters only, so the\nJSON body below is unsigned.\n",
            "example": "4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b",
            "in": "query",
            "name": "sig",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "actualDuration": 5,
                "adId": "64f000000000000000000001",
                "completionType": "full",
                "deviceInfo": {
                  "native": {
                    "appBundle": "com.szbjkj.bajietouchpower",
                    "packageName": "com.szbjkj.bajietouchpower"
                  }
                },
                "impressionId": "imp_pop_1",
                "playEndTime": "2026-09-01T00:00:00.000Z",
                "playStartTime": "2026-08-31T23:59:55.000Z",
                "request_id": "req-pop-1",
                "scheduledDuration": 15,
                "screenId": "507f1f77bcf86cd799439022"
              },
              "schema": {
                "properties": {
                  "actualDuration": {
                    "description": "Seconds actually played. When omitted it is derived as\n`(playEndTime - playStartTime) / 1000`.\n",
                    "example": 5,
                    "format": "double",
                    "type": "number"
                  },
                  "adId": {
                    "description": "Advertisement id. Must be a JSON **string** — a numeric value\nreaches an unguarded `startsWith` and produces a 500.\n",
                    "example": "64f000000000000000000001",
                    "type": "string"
                  },
                  "ad_source": {
                    "description": "Also accepted as `adSource`, `source`, `src`. Self-promotion\nlabels here suppress both the billable row and the `burl`.\n",
                    "type": "string"
                  },
                  "ad_type": {
                    "description": "Also accepted as `adType`.",
                    "type": "string"
                  },
                  "allocation_id": {
                    "description": "Also accepted as `allocationId`, and read from the query\nstring when absent from the body.\n",
                    "type": "string"
                  },
                  "campaignId": {
                    "type": "string"
                  },
                  "channel": {
                    "description": "Delivery channel. Forced to `ctv` when the `User-Agent`\nidentifies the CTV agent, otherwise this value, otherwise\n`dooh`.\n",
                    "example": "dooh",
                    "type": "string"
                  },
                  "completionType": {
                    "description": "How the play ended. Only `full` and `complete` are billable;\nany other value is recorded but never settles revenue. The\nfield is not enum-validated server-side, so an unrecognised\nvalue is accepted and simply never bills.\n",
                    "example": "full",
                    "type": "string"
                  },
                  "creative_id": {
                    "description": "Also accepted as `creativeId`. When omitted it is derived\nfrom `adId` by stripping a leading `ima_`.\n",
                    "type": "string"
                  },
                  "deviceInfo": {
                    "description": "Device and player context captured at play time.",
                    "properties": {
                      "audioEnabled": {
                        "example": false,
                        "type": "boolean"
                      },
                      "native": {
                        "properties": {
                          "appBundle": {
                            "example": "com.szbjkj.bajietouchpower",
                            "type": "string"
                          },
                          "packageName": {
                            "example": "com.szbjkj.bajietouchpower",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "orientation": {
                        "example": "landscape",
                        "type": "string"
                      },
                      "resolution": {
                        "example": "1920x1080",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "displayContext": {
                    "description": "Presentation the ad rendered in. `displayMode`/`mode` and\n`displayAreaRatio`/`areaRatio` are both accepted spellings.\n",
                    "properties": {
                      "displayAreaRatio": {
                        "example": 1,
                        "format": "double",
                        "type": "number"
                      },
                      "displayMode": {
                        "example": "fullscreen",
                        "type": "string"
                      },
                      "lBarPosition": {
                        "nullable": true,
                        "type": "string"
                      }
                    },
                    "type": "object"
                  },
                  "fill_source": {
                    "description": "Also accepted as `fillSource`. Read here only — the\nimpression beacon ignores it.\n",
                    "type": "string"
                  },
                  "impressionId": {
                    "description": "Impression id from the serve. **Optional**, despite what\nolder integration notes say — when omitted, the generated\n`playId` is substituted into the signed payload, which\nchanges what a verifier must reconstruct.\n",
                    "example": "imp_1",
                    "type": "string"
                  },
                  "playEndTime": {
                    "description": "Defaults to the server clock.",
                    "example": "2026-09-01T00:00:00.000Z",
                    "format": "date-time",
                    "type": "string"
                  },
                  "playStartTime": {
                    "description": "Defaults to the server clock. Supplies the signed timestamp.",
                    "example": "2026-08-31T23:59:55.000Z",
                    "format": "date-time",
                    "type": "string"
                  },
                  "request_id": {
                    "description": "Auction id. Also accepted as `requestId`, `bid_request_id`,\n`bidRequestId`, and — uniquely on this operation — read from\nthe query string when absent from the body, then from\n`auctionId`/`auction_id`.\n",
                    "example": "req-pop-1",
                    "type": "string"
                  },
                  "scheduledDuration": {
                    "description": "Seconds the slot was scheduled for.",
                    "example": 15,
                    "format": "double",
                    "type": "number"
                  },
                  "screenId": {
                    "description": "Screen the ad played on.",
                    "example": "507f1f77bcf86cd799439022",
                    "type": "string"
                  },
                  "screenStatus": {
                    "description": "Defaults to `functional`.",
                    "example": "functional",
                    "type": "string"
                  },
                  "winPrice": {
                    "description": "Clearing price for this play, when the caller knows it.",
                    "format": "double",
                    "type": "number"
                  }
                },
                "required": [
                  "screenId",
                  "adId",
                  "completionType"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "givt": {
                    "flags": [],
                    "passed": true
                  },
                  "message": "Proof-of-play recorded",
                  "playId": "pop_507f1f77bcf86cd799439022_1788134400000_a1b2c3d4",
                  "screenOnline": true,
                  "signature": "ed25519=3f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e8f9a0b1c2d",
                  "signatureVersion": "v2",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "givt": {
                      "description": "General-invalid-traffic verdict.",
                      "properties": {
                        "flags": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "passed": {
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    },
                    "message": {
                      "type": "string"
                    },
                    "playId": {
                      "description": "Server-minted play id,\n`pop_<screenId>_<epochMs>_<8 hex chars>`. Substituted for\n`impressionId` in the signed payload when you omitted it.\n",
                      "type": "string"
                    },
                    "reason": {
                      "description": "Present only when `success` is `false`.",
                      "type": "string"
                    },
                    "screenOnline": {
                      "description": "Whether the screen's heartbeat was fresher than 90 seconds\nat the time of the call.\n",
                      "type": "boolean"
                    },
                    "signature": {
                      "description": "Verification receipt. `ed25519=<hex>` normally, or\n`sha256=<hex>` when no Ed25519 key is configured.\n",
                      "type": "string"
                    },
                    "signatureVersion": {
                      "description": "`v2` = Ed25519, `v1` = HMAC-SHA256 fallback.",
                      "enum": [
                        "v1",
                        "v2"
                      ],
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Proof-of-play recorded. Also the status returned when the attribution\nsignature was rejected — in that case `success` is `false` and\n`reason` is `invalid_attribution_signature`, and nothing was\nrecorded. **Check `success` before treating the play as settled.**\n\nA failed invalid-traffic check does NOT change the status either: it\nreturns `200` with `givt.passed: false`, and suppresses the billable\nrow and the `burl`.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Missing required parameters (screenId, adId, completionType)"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`screenId`, `adId`, or `completionType` is missing."
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal server error"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error. Nothing is guaranteed to have been recorded;\nbecause this endpoint does not deduplicate, treat a retry as\npotentially double-billing.\n"
          }
        },
        "security": [],
        "summary": "Record MRC DOOH proof-of-play",
        "tags": [
          "Impression Tracking"
        ],
        "x-codeSamples": [
          {
            "label": "cURL",
            "lang": "curl",
            "source": "# `sig` covers the QUERY parameters only — the JSON body is unsigned.\ncurl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v1/proof-of-play?sig=$SIG\" \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n        \"screenId\": \"507f1f77bcf86cd799439022\",\n        \"adId\": \"64f000000000000000000001\",\n        \"impressionId\": \"imp_pop_1\",\n        \"completionType\": \"full\",\n        \"playStartTime\": \"2026-08-31T23:59:55.000Z\",\n        \"playEndTime\": \"2026-09-01T00:00:00.000Z\",\n        \"actualDuration\": 5,\n        \"scheduledDuration\": 15,\n        \"request_id\": \"req-pop-1\"\n      }'\n"
          },
          {
            "label": "Node.js — settle one play, safely",
            "lang": "javascript",
            "source": "// THIS ENDPOINT BILLS AND DOES NOT DEDUPLICATE. Every accepted call\n// can mint a fresh billable row, so a blind retry double-bills.\n// Retry only when you got no response at all.\nconst res = await fetch(\n  `https://api.trillboards.com/openrtb/v1/proof-of-play?sig=${sig}`,\n  {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json' },\n    body: JSON.stringify({\n      screenId, adId, impressionId,\n      completionType: 'full',\n      playStartTime, playEndTime,\n      actualDuration: 5,\n      scheduledDuration: 15,\n      request_id: requestId,\n    }),\n  },\n);\n\nconst body = await res.json();\n\n// A 200 is NOT proof of settlement. A rejected signature and a\n// failed invalid-traffic check both return 200.\nif (!body.success) {\n  throw new Error(`not settled: ${body.reason}`);\n}\nif (body.givt && body.givt.passed === false) {\n  // Recorded, but suppressed for invalid traffic — no revenue.\n  log.warn({ flags: body.givt.flags }, 'play failed GIVT');\n}\n// `body.signature` is your verifiable receipt for this play.\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast": {
      "get": {
        "description": "Returns an inline VAST document for one advertisement.\n\n## Two different documents come out of this path\n\nWhich one you get is decided by whether you send `screenId`, and they\nare not the same VAST version:\n\n- **`screenId` supplied, and a servable owner placement exists** —\n  a **VAST 3.0** owner-direct document. Its tracking URLs are signed\n  and carry owner-billing parameters, and it has no\n  `ViewableImpression` and no `AdVerifications`.\n- **No `screenId`** — a **VAST 4.2** programmatic document with\n  `ViewableImpression` and an OMSDK `AdVerifications` extension.\n\nSo a client that hardcodes an expectation of 4.2 will break the moment\nit starts sending `screenId`. Parse the version rather than assuming\nit, and prefer following the wrapper as any compliant player does.\n\n## There is no empty-VAST no-fill on this path\n\nA request that cannot be filled is a `404` with a plain-text body, not\na `200` carrying an empty `<VAST/>`. That differs from\n`GET /openrtb/v1/vast/{partnerKey}/{screenId}`, which always answers\n`200` and uses an empty document to signal no-fill. A player wired to\nthis endpoint must treat a non-200 as no-fill.\n",
        "operationId": "generateVast",
        "parameters": [
          {
            "$ref": "#/components/parameters/ScreenIdQuery"
          },
          {
            "description": "Advertisement ID",
            "in": "query",
            "name": "adid",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Impression ID for tracking correlation",
            "in": "query",
            "name": "impid",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Video width in pixels",
            "in": "query",
            "name": "w",
            "schema": {
              "default": 1920,
              "type": "integer"
            }
          },
          {
            "description": "Video height in pixels",
            "in": "query",
            "name": "h",
            "schema": {
              "default": 1080,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "examples": {
                  "ownerDirect": {
                    "summary": "VAST 3.0 — screenId supplied, owner-direct placement",
                    "value": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<VAST version=\"3.0\">\n  <Ad id=\"64f000000000000000000001\">\n    <InLine>\n      <AdSystem version=\"1.0\">Trillboards DOOH</AdSystem>\n      <AdTitle>Acme Summer Promo</AdTitle>\n      <Error><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=error&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&errorCode=[ERRORCODE]]]></Error>\n      <Impression><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=impression&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=imp456]]></Impression>\n      <Creatives>\n        <Creative id=\"64f000000000000000000001\" sequence=\"1\">\n          <Linear>\n            <Duration>00:00:30</Duration>\n            <TrackingEvents>\n              <Tracking event=\"start\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=start&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=imp456]]></Tracking>\n              <Tracking event=\"firstQuartile\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=firstQuartile&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=imp456]]></Tracking>\n              <Tracking event=\"midpoint\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=midpoint&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=imp456]]></Tracking>\n              <Tracking event=\"thirdQuartile\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=thirdQuartile&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=imp456]]></Tracking>\n              <Tracking event=\"complete\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=complete&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=imp456]]></Tracking>\n            </TrackingEvents>\n            <MediaFiles>\n              <MediaFile delivery=\"progressive\" type=\"video/mp4\" width=\"1920\" height=\"1080\"><![CDATA[https://cdn.trillboards.com/creative-media/sha256/abc.mp4]]></MediaFile>\n            </MediaFiles>\n          </Linear>\n        </Creative>\n      </Creatives>\n    </InLine>\n  </Ad>\n</VAST>\n"
                  },
                  "programmatic": {
                    "summary": "VAST 4.2 — no screenId",
                    "value": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<VAST version=\"4.2\" xmlns=\"http://www.iab.com/VAST\">\n  <Ad id=\"64f000000000000000000001\">\n    <InLine>\n      <AdSystem version=\"1.0\">Trillboards DOOH</AdSystem>\n      <AdTitle>Acme Summer Promo</AdTitle>\n      <Impression><![CDATA[https://api.trillboards.com/openrtb/v1/impression?adid=64f000000000000000000001&impid=imp456&event=impression]]></Impression>\n      <ViewableImpression id=\"imp456\">\n        <Viewable><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=viewable]]></Viewable>\n        <NotViewable><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=notviewable]]></NotViewable>\n        <ViewUndetermined><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=viewundetermined]]></ViewUndetermined>\n      </ViewableImpression>\n      <Creatives>\n        <Creative id=\"64f000000000000000000001\" sequence=\"1\">\n          <Linear>\n            <Duration>00:00:30</Duration>\n            <TrackingEvents>\n              <Tracking event=\"start\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=start]]></Tracking>\n              <Tracking event=\"firstQuartile\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=firstQuartile]]></Tracking>\n              <Tracking event=\"midpoint\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=midpoint]]></Tracking>\n              <Tracking event=\"thirdQuartile\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=thirdQuartile]]></Tracking>\n              <Tracking event=\"complete\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&impid=imp456&event=complete]]></Tracking>\n            </TrackingEvents>\n            <MediaFiles>\n              <MediaFile delivery=\"progressive\" type=\"video/mp4\" width=\"1920\" height=\"1080\">\n                <![CDATA[https://cdn.trillboards.com/ads/promo.mp4]]>\n              </MediaFile>\n            </MediaFiles>\n          </Linear>\n        </Creative>\n      </Creatives>\n      <Extensions>\n        <Extension type=\"AdVerifications\">\n          <AdVerification vendor=\"trillboards\">\n            <JavaScriptResource apiFramework=\"omid\" browserOptional=\"true\">\n              <![CDATA[https://api.trillboards.com/openrtb/v1/omsdk/verification.js]]>\n            </JavaScriptResource>\n            <VerificationParameters>\n              <![CDATA[{\"sessionId\":\"omsdk_507f1f77bcf86cd799439022_1788134400000\"}]]>\n            </VerificationParameters>\n            <TrackingEvents>\n              <Tracking event=\"verificationNotExecuted\">\n                <![CDATA[https://api.trillboards.com/openrtb/v1/omsdk/event?sid=omsdk_507f1f77bcf86cd799439022_1788134400000&event=verificationNotExecuted&reason=[REASON]]]>\n              </Tracking>\n            </TrackingEvents>\n          </AdVerification>\n        </Extension>\n      </Extensions>\n    </InLine>\n  </Ad>\n</VAST>\n"
                  }
                },
                "schema": {
                  "description": "VAST 4.2 on the programmatic branch; VAST 3.0 on the\nowner-direct branch.\n",
                  "type": "string"
                }
              }
            },
            "description": "An inline VAST document. See the two branches above — the version\ndepends on whether you sent `screenId`.\n"
          },
          "404": {
            "content": {
              "text/html": {
                "example": "Video ad not found",
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "No servable video ad for this request. The body is the bare string\n`Video ad not found`, not a JSON error envelope — this operation\npredates the shared error model and has not been migrated. Do not\n`JSON.parse` it.\n\nThe media type is `text/html; charset=utf-8`: the handler passes a\nplain string to `res.send` without setting a content type, which is\nwhat Express defaults to. It is not HTML, and it is not\n`text/plain`. `GET /openrtb/v1/win` returns its string bodies the\nsame way.\n"
          }
        },
        "security": [],
        "summary": "Generate a VAST document for an ad",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-codeSamples": [
          {
            "label": "cURL — programmatic branch (VAST 4.2)",
            "lang": "curl",
            "source": "curl -sS -G https://api.trillboards.com/openrtb/v1/vast \\\n  -d adid=64f000000000000000000001 \\\n  -d impid=imp456 \\\n  -d w=1920 -d h=1080\n"
          },
          {
            "label": "cURL — owner-direct branch (VAST 3.0)",
            "lang": "curl",
            "source": "# Adding screenId selects a DIFFERENT document and a different VAST\n# version. Do not hardcode a version expectation.\ncurl -sS -G https://api.trillboards.com/openrtb/v1/vast \\\n  -d adid=64f000000000000000000001 \\\n  -d screenId=507f1f77bcf86cd799439022 \\\n  -d impid=imp456\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast-cache-metric": {
      "post": {
        "description": "Reports one VAST video cache hit or miss from a screen, so bandwidth\nsavings can be attributed.\n\n`cache_hit` is the only required field and is **strictly typed** — it\nmust be a JSON boolean. The string `\"true\"` is rejected with a `400`.\nEvery other field is optional and, when absent, defaults as documented\nbelow rather than erroring.\n\n## A 200 does not mean the row landed\n\nBoth the PostgreSQL insert and the ClickHouse mirror are best-effort:\neach is wrapped in its own error trap that logs and continues, and the\ncreative-media side effect is not awaited at all. The `200` confirms the\npayload was accepted and validated, nothing more. There is no storage\nfailure path that produces a `5xx`.\n\n## Content-Type must be JSON\n\nOnly `application/json` is parsed. A `navigator.sendBeacon` call that\ndefaults to `text/plain` arrives with an empty body and is rejected as a\nmissing `cache_hit`. Construct the `Blob` with an explicit\n`{ type: 'application/json' }`.\n",
        "operationId": "recordVastCacheMetric",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "cache_hit": true,
                "content_type": "video/mp4",
                "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                "device_class": "tablet",
                "file_size_bytes": 5242880,
                "media_url": "https://cdn.trillboards.com/creatives/5f2a1b3c.mp4",
                "screen_id": "69e2e204282d72f900211ed5"
              },
              "schema": {
                "properties": {
                  "cache_hit": {
                    "description": "Whether the video was served from the local cache. Must be a\nreal boolean.\n",
                    "example": true,
                    "type": "boolean"
                  },
                  "cache_usage_bytes": {
                    "description": "Total bytes the on-device cache currently holds.",
                    "format": "int64",
                    "type": "integer"
                  },
                  "content_type": {
                    "example": "video/mp4",
                    "type": "string"
                  },
                  "creative_id": {
                    "description": "A leading `ima_` prefix is stripped before storage.",
                    "type": "string"
                  },
                  "device_class": {
                    "description": "Defaults to `unknown`.",
                    "example": "tablet",
                    "type": "string"
                  },
                  "device_id": {
                    "type": "string"
                  },
                  "error": {
                    "description": "Fetch error string, when the attempt failed.",
                    "type": "string"
                  },
                  "eviction_triggered": {
                    "description": "Whether storing this asset evicted another. Defaults to `false`.",
                    "type": "boolean"
                  },
                  "fetch_time_ms": {
                    "description": "Milliseconds spent fetching on a miss. Defaults to null.",
                    "example": 420,
                    "type": "integer"
                  },
                  "file_size_bytes": {
                    "description": "Size of the video asset. Defaults to `0`.",
                    "example": 5242880,
                    "format": "int64",
                    "type": "integer"
                  },
                  "fingerprint": {
                    "type": "string"
                  },
                  "media_url": {
                    "description": "Asset URL. Also accepted as `mediaUrl`, but the camelCase\nspelling reaches only the ClickHouse mirror and the\ncreative-media side effect — **not** the PostgreSQL row. Send\n`media_url` for complete attribution.\n",
                    "type": "string"
                  },
                  "request_id": {
                    "type": "string"
                  },
                  "screen_id": {
                    "example": "69e2e204282d72f900211ed5",
                    "type": "string"
                  },
                  "timestamp": {
                    "description": "Defaults to the server clock.",
                    "format": "date-time",
                    "type": "string"
                  },
                  "variant_name": {
                    "description": "Defaults to `unknown`.",
                    "type": "string"
                  }
                },
                "required": [
                  "cache_hit"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "success": true
                },
                "schema": {
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Metric accepted. The body is exactly `{\"success\": true}` — no id, no\necho, and no confirmation of durable storage.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "validation_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#validation_error",
                    "message": "cache_hit is required and must be boolean.",
                    "param": "cache_hit",
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "invalid_request_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "`cache_hit` is absent or is not a JSON boolean."
          }
        },
        "security": [],
        "summary": "Track VAST cache metric",
        "tags": [
          "Bandwidth Telemetry"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast-cache/resolve/{creativeId}": {
      "get": {
        "description": "Returns the resolved cache entry for `creativeId` — the cached media\nURL on Trillboards' CDN, content-length, MIME type, first-seen and\nlast-seen timestamps, plus the upstream source URL. Screens hit this\nendpoint after parsing a `<MediaFile>` element to decide whether to\nstream from cache or upstream.\n",
        "operationId": "resolveVastCacheEntry",
        "parameters": [
          {
            "in": "path",
            "name": "creativeId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "cache_url": {
                      "format": "uri",
                      "type": "string"
                    },
                    "content_length": {
                      "type": "integer"
                    },
                    "content_type": {
                      "type": "string"
                    },
                    "creative_id": {
                      "type": "string"
                    },
                    "first_seen_at": {
                      "format": "date-time",
                      "type": "string"
                    },
                    "last_seen_at": {
                      "format": "date-time",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Cache entry"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "No cache entry for `creativeId`"
          }
        },
        "security": [],
        "summary": "Resolve a cached VAST creative by ID",
        "tags": [
          "VAST Telemetry"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast-request": {
      "post": {
        "description": "Track pending, filled, no-fill, error, timeout, and completion states.",
        "operationId": "recordVastRequest",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "success": true
                },
                "schema": {
                  "properties": {
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Telemetry accepted. The body is exactly `{\"success\": true}` — there\nis no id, no echo, and no per-field verdict, so there is nothing to\ncorrelate a later lookup against beyond the `request_id` you sent.\n\nThe acknowledgement is returned **before** creative side effects\nrun, so a `200` means the row was accepted, not that downstream\ncreative processing finished.\n\nThis is a `sendBeacon` target and is deliberately unauthenticated;\nit accepts a `text/plain` body as well as JSON, because\n`navigator.sendBeacon` cannot set a `Content-Type` header.\n"
          }
        },
        "security": [],
        "summary": "Track VAST request outcome",
        "tags": [
          "VAST Telemetry"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast-request/analytics": {
      "get": {
        "description": "Fill-rate and latency outcomes for VAST requests, aggregated by waterfall\nvariant over a bounded time window.\n\n## Rates here are fractions, not percentages\n\n`fill_rate`, `completion_rate`, and `effective_rate` are values in `0..1`\nrounded to four decimal places — a fill rate of 8% is `0.08`. The cache\nanalytics endpoints on this same surface report percentages instead\n(`hit_rate_pct`, `0..100`), so do not share a formatter between them.\n\n## The window is clamped\n\nThe lookback is capped at 14 days: a `start_date` older than that is\nsilently moved forward, an `end_date` in the future is pulled back to\nnow, and an `end_date` before the effective start is pushed up to it. An\nunparseable date falls back to the default rather than erroring. Read\n`period.start`, `period.end`, and `period.clamped` from the response to\nlearn the window you were actually served — never assume you got the\nwindow you asked for.\n\nServed from ClickHouse, falling back to an hourly PostgreSQL rollup. Both\nengines produce the same field set, so the source is not distinguishable\nfrom the response.\n",
        "operationId": "getVastRequestAnalytics",
        "parameters": [
          {
            "description": "Window start. Defaults to 24 hours ago. Clamped forward to at most\n14 days ago.\n",
            "example": "2026-08-25T00:00:00.000Z",
            "in": "query",
            "name": "start_date",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Window end. Defaults to now, and is never allowed past now or before\nthe effective start.\n",
            "example": "2026-09-01T00:00:00.000Z",
            "in": "query",
            "name": "end_date",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Grouping dimension. `device_category` switches the breakdown; every\nother value — including a misspelling — silently groups by variant\nwhile still being echoed back verbatim in `group_by`. Compare the\nechoed value against what you sent to detect a typo.\n",
            "example": "device_category",
            "in": "query",
            "name": "group_by",
            "required": false,
            "schema": {
              "enum": [
                "variant",
                "device_category"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "group_by": "variant",
                  "period": {
                    "clamped": false,
                    "end": "2026-09-01T00:00:00.000Z",
                    "max_lookback_days": 14,
                    "start": "2026-08-25T00:00:00.000Z"
                  },
                  "variants": [
                    {
                      "avg_latency_ms": 751,
                      "completed": 300,
                      "completion_rate": 0.6667,
                      "effective_rate": 0.06,
                      "errors": 100,
                      "fill_rate": 0.09,
                      "filled": 450,
                      "no_fill": 4400,
                      "timeouts": 50,
                      "total_requests": 5000,
                      "variant_name": "floodgates_v2"
                    },
                    {
                      "avg_latency_ms": 1200,
                      "completed": 50,
                      "completion_rate": 0.0625,
                      "effective_rate": 0.005,
                      "errors": 100,
                      "fill_rate": 0.08,
                      "filled": 800,
                      "no_fill": 9000,
                      "timeouts": 100,
                      "total_requests": 10000,
                      "variant_name": "gam_v1"
                    }
                  ]
                },
                "schema": {
                  "properties": {
                    "group_by": {
                      "description": "Echo of the requested grouping, unvalidated.",
                      "type": "string"
                    },
                    "period": {
                      "properties": {
                        "clamped": {
                          "description": "True when either endpoint was moved. Your requested\nwindow was not honoured as sent.\n",
                          "type": "boolean"
                        },
                        "end": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "max_lookback_days": {
                          "description": "Always 14.",
                          "type": "integer"
                        },
                        "start": {
                          "format": "date-time",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "variants": {
                      "items": {
                        "properties": {
                          "avg_latency_ms": {
                            "description": "Mean latency, rounded to a whole millisecond.",
                            "type": "integer"
                          },
                          "completed": {
                            "type": "integer"
                          },
                          "completion_rate": {
                            "description": "`completed / filled`, a fraction in 0..1.",
                            "format": "double",
                            "type": "number"
                          },
                          "effective_rate": {
                            "description": "`completed / total_requests`, a fraction in 0..1.",
                            "format": "double",
                            "type": "number"
                          },
                          "errors": {
                            "type": "integer"
                          },
                          "fill_rate": {
                            "description": "`filled / total_requests`, a fraction in 0..1.",
                            "format": "double",
                            "type": "number"
                          },
                          "filled": {
                            "type": "integer"
                          },
                          "no_fill": {
                            "type": "integer"
                          },
                          "timeouts": {
                            "type": "integer"
                          },
                          "total_requests": {
                            "type": "integer"
                          },
                          "variant_name": {
                            "description": "Variant, or device category when grouped that way.\nAn unattributed row is reported as `unknown`.\n",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Fill-rate outcomes for the served window."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized__programmatic"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Admin access required",
                  "statusCode": 403
                },
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "statusCode": {
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Authenticated but not an operator. This response is unscoped fleet-wide —\nthe handler applies no tenant or screen filter — so it requires the admin\nrole, not merely a session.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "internal_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#internal_error",
                    "message": "An internal error occurred. Please contact support with request_id req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c.",
                    "param": null,
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "api_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "The analytics backend failed. Emitted as the canonical error\nenvelope.\n"
          }
        },
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "summary": "Get VAST request analytics",
        "tags": [
          "VAST Telemetry"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast-test/{screenId}": {
      "get": {
        "description": "Sandbox endpoint that returns a guaranteed-fill VAST response built\nfrom a small set of test creatives. Used during partner integration\nto validate that the screen's VAST parser handles the full set of\nIAB-spec-required elements. The returned creative is NOT a billable\nad.\n",
        "operationId": "testVastForScreen",
        "parameters": [
          {
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Test VAST 4.2 XML document"
          }
        },
        "security": [],
        "summary": "Generate a test VAST response for a screen",
        "tags": [
          "Sandbox"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast-wrap/{provider}/{screenId}": {
      "get": {
        "description": "Returns a VAST 4.2 document where the inner `<Wrapper>` element points\nat the demand-partner's VAST endpoint, while the outer document carries\nTrillboards' tracking macros (impression pixel, proof-of-play). Used\nwhen bidding partners require redirect-style ad serving.\n",
        "operationId": "getVastWrapperByProvider",
        "parameters": [
          {
            "description": "Opaque provider alias issued at onboarding. Treat as a secret.",
            "in": "path",
            "name": "provider",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "VAST 4.2 wrapper XML"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "No bid for this screen + provider"
          }
        },
        "security": [],
        "summary": "Generate a partner-wrapped VAST response",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast/direct/{screenId}/{adid}": {
      "get": {
        "description": "Stable URL used by managed-service publishers that configure a VAST tag\ndirectly in their own player. The URL itself remains fixed, while every\nrequest receives a fresh impression identity and signed lifecycle tracking\nbeacons. The endpoint fails closed unless the advertisement has a currently\nservable placement on the requested screen.\n\nThe response is a VAST 3.0 InLine document containing a progressive H.264\nMP4 MediaFile plus signed impression, quartile, completion, error, and\nproof-of-play reporting URLs. Responses are never cached.\n",
        "operationId": "getFixedDirectVastTag",
        "parameters": [
          {
            "description": "Trillboards Mongo-format screen ID bound to the booking",
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-fA-F]{24}$",
              "type": "string"
            }
          },
          {
            "description": "Trillboards Mongo-format advertisement ID bound to the booking",
            "in": "path",
            "name": "adid",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-fA-F]{24}$",
              "type": "string"
            }
          },
          {
            "description": "Optional device fingerprint included in signed playback telemetry",
            "in": "query",
            "name": "deviceId",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Declared MediaFile width in pixels",
            "in": "query",
            "name": "w",
            "schema": {
              "default": 1920,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Declared MediaFile height in pixels",
            "in": "query",
            "name": "h",
            "schema": {
              "default": 1080,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "example": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<VAST version=\"3.0\">\n  <Ad id=\"64f000000000000000000001\">\n    <InLine>\n      <AdSystem version=\"1.0\">Trillboards DOOH</AdSystem>\n      <AdTitle>Acme Summer Promo</AdTitle>\n      <Error><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=error&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&errorCode=[ERRORCODE]]]></Error>\n      <Impression><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=impression&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=fixed_direct_9c1f0a7b3e5d2c8a4f6b0d1e]]></Impression>\n      <Creatives>\n        <Creative id=\"64f000000000000000000001\" sequence=\"1\">\n          <Linear>\n            <Duration>00:00:30</Duration>\n            <TrackingEvents>\n              <Tracking event=\"start\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=start&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=fixed_direct_9c1f0a7b3e5d2c8a4f6b0d1e]]></Tracking>\n              <Tracking event=\"firstQuartile\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=firstQuartile&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=fixed_direct_9c1f0a7b3e5d2c8a4f6b0d1e]]></Tracking>\n              <Tracking event=\"midpoint\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=midpoint&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=fixed_direct_9c1f0a7b3e5d2c8a4f6b0d1e]]></Tracking>\n              <Tracking event=\"thirdQuartile\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=thirdQuartile&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=fixed_direct_9c1f0a7b3e5d2c8a4f6b0d1e]]></Tracking>\n              <Tracking event=\"complete\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=64f000000000000000000001&screenId=507f1f77bcf86cd799439022&event=complete&ingestion_path=owned_direct&src=owned_direct&sig=4b9ffbc2c7dd0a1e2f3b4c5d6e7f8a9b&impid=fixed_direct_9c1f0a7b3e5d2c8a4f6b0d1e]]></Tracking>\n            </TrackingEvents>\n            <MediaFiles>\n              <MediaFile delivery=\"progressive\" type=\"video/mp4\" width=\"1920\" height=\"1080\"><![CDATA[https://cdn.trillboards.com/creative-media/sha256/abc.mp4]]></MediaFile>\n            </MediaFiles>\n          </Linear>\n        </Creative>\n      </Creatives>\n    </InLine>\n  </Ad>\n</VAST>\n",
                "schema": {
                  "description": "VAST 3.0 XML with H.264 MediaFile and signed lifecycle tracking beacons",
                  "type": "string"
                }
              }
            },
            "description": "Fresh VAST 3.0 InLine document for the booked ad and screen",
            "headers": {
              "Cache-Control": {
                "description": "Prevents reuse of per-play tracking identities",
                "schema": {
                  "example": "no-store, no-cache, must-revalidate, private",
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Advertisement is missing, has no playable MP4, or is not currently servable on the screen"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Request rate limit exceeded"
          }
        },
        "security": [],
        "summary": "Serve a fixed first-party VAST tag for one booked ad and screen",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast/{partnerKey}/{screenId}": {
      "get": {
        "description": "Path-parameterised entry-point used during partner onboarding to\nkeep partner-specific configuration (creative whitelist, eCPM\nfloor, deal IDs, custom macros) indexed by `partnerKey`. The\n`partnerKey` is issued at onboarding and MUST match the variant\nassigned to the caller's API key, or the request is rejected.\n\nThe returned document is the same VAST 4.2 shape produced by\n`GET /openrtb/v1/vast`; the only difference is that partner-specific\nconfiguration is selected by `partnerKey` rather than by API-key\nlookup, which lets a single partner test multiple configurations\nside-by-side from one credential.\n",
        "operationId": "getVastByPartnerVariant",
        "parameters": [
          {
            "description": "Opaque partner-variant alias issued at onboarding. Treat as a secret — do not guess values.",
            "in": "path",
            "name": "partnerKey",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional advertisement ID override. Defaults to waterfall winner.",
            "in": "query",
            "name": "adid",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "w",
            "schema": {
              "default": 1920,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "h",
            "schema": {
              "default": 1080,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/xml": {
                "examples": {
                  "filled": {
                    "summary": "Fill — the partner's document with our pixels injected",
                    "value": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<VAST version=\"3.0\">\n  <Ad id=\"partner-creative-8841\">\n    <InLine>\n      <AdSystem version=\"1.0\">Partner Exchange</AdSystem>\n      <AdTitle>Acme Summer Promo</AdTitle>\n      <Impression><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=partner-creative-8841&screenId=507f1f77bcf86cd799439022&event=impression&request_id=req-pop-1]]></Impression>\n      <Creatives>\n        <Creative id=\"partner-creative-8841\" sequence=\"1\">\n          <Linear>\n            <Duration>00:00:15</Duration>\n            <TrackingEvents>\n              <Tracking event=\"start\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=partner-creative-8841&screenId=507f1f77bcf86cd799439022&event=start&request_id=req-pop-1]]></Tracking>\n              <Tracking event=\"complete\"><![CDATA[https://api.trillboards.com/openrtb/v1/event?adid=partner-creative-8841&screenId=507f1f77bcf86cd799439022&event=complete&request_id=req-pop-1]]></Tracking>\n            </TrackingEvents>\n            <MediaFiles>\n              <MediaFile delivery=\"progressive\" type=\"video/mp4\" width=\"1920\" height=\"1080\"><![CDATA[https://cdn.partner.example/creatives/8841.mp4]]></MediaFile>\n            </MediaFiles>\n          </Linear>\n        </Creative>\n      </Creatives>\n    </InLine>\n  </Ad>\n</VAST>\n"
                  },
                  "noFill": {
                    "summary": "No-fill — 200 with an empty document",
                    "value": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"3.0\"/>\n"
                  }
                },
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "A VAST document — **or an empty one, which is how no-fill is\nsignalled on this path.**\n\n## This endpoint essentially always returns 200\n\nNo-fill, a partner timeout, a partner 5xx, an unservable screen and\nan internal error all return `200` with the body\n`<?xml version=\"1.0\" encoding=\"UTF-8\"?><VAST version=\"3.0\"/>`.\nThat is deliberate: the caller is a player, and a non-200 renders\nas a broken ad slot rather than as an empty break. The consequence\nis that **you must inspect the document to know whether you were\nfilled** — a status check tells you nothing. Look for an `<Ad>`\nelement; an empty `<VAST/>` has none.\n\nThe only non-200 is a `404` for an unrecognised `partnerKey`.\n\n## The document is the partner's, not ours\n\nOn a fill this is the winning bidder's own `adm`, passed through a\ncreative filter and then rewritten to inject our tracking pixels.\nWe do not author it, so **its VAST version is whatever the partner\nsent** — do not rely on 4.2, and follow wrappers as any compliant\nplayer does.\n\nSent with `Cache-Control: no-store, no-cache, must-revalidate,\nproxy-revalidate` on every response, filled or not, so a no-fill is\nnever cached over a later fill.\n"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Partner-variant alias does not match the caller's API key"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Screen or ad not found"
          }
        },
        "security": [],
        "summary": "Generate VAST response on a partner-variant alias",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      },
      "options": {
        "operationId": "preflightVastByPartnerVariant",
        "parameters": [
          {
            "in": "path",
            "name": "partnerKey",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "CORS preflight response"
          }
        },
        "security": [],
        "summary": "CORS preflight for the partner-variant VAST endpoint",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/vast/{partnerKey}/{screenId}/debug": {
      "get": {
        "description": "Returns the full waterfall decision log for the most recent VAST\nrequest on this `partnerKey` + `screenId` pair: per-bidder bids,\nno-bid reasons, cache lookups, blocklist hits, and timing\nbreakdowns. Intended for live debugging during partner onboarding.\nRate-limited per partner-variant.\n",
        "operationId": "debugVastByPartnerVariant",
        "parameters": [
          {
            "in": "path",
            "name": "partnerKey",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "description": "Per-bidder waterfall details. Shape mirrors `serving_decisions` rows.",
                  "type": "object"
                }
              }
            },
            "description": "Waterfall decision log + per-bidder breakdown"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Partner-variant alias does not match the caller's API key"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "No recent waterfall for this screen + partner-variant pair"
          }
        },
        "security": [],
        "summary": "Debug view of VAST waterfall for a partner-variant alias",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/viewability": {
      "post": {
        "description": "Records viewable-time telemetry against an impression already recorded by\nthe impression beacon. Not billable — it only updates viewability columns\non the existing impression row.\n\nThis is the only beacon on this surface with **no attribution signature\nand no rate limit**; it is completely open.\n\nAll input combinations return the documented `200`, including\n`viewable: true` with `viewableTime >= 1000`.\n\n> **Fixed.** Until this release, `viewable: true` **and**\n> `viewableTime >= 1000` answered `500 {\"error\":\"Internal server error\"}`:\n> the route wires the handler as a bare static reference, so `this` was\n> `undefined` and a sibling call threw. Because the PostgreSQL write is\n> awaited *before* that branch, the telemetry was stored and the caller\n> was told it had failed. Retrying was harmless — the write is an\n> idempotent `UPDATE` keyed on `impressionId`, never an insert, so a\n> retry rewrote the same row with the same values and could not duplicate\n> anything. Nothing needs to be resent or reconciled.\n\n`screenSize`, `adSize`, and `intersection` are parsed off the body and\nthen discarded — sending them has no effect.\n",
        "operationId": "trackViewability",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "adId": "ad123",
                "impressionId": "imp456",
                "percentInView": 65,
                "viewable": false,
                "viewableTime": 800
              },
              "schema": {
                "properties": {
                  "adId": {
                    "description": "Advertisement id. Must be a JSON **string** — a numeric value\nreaches an unguarded `startsWith` and produces a 500.\n",
                    "example": "ad123",
                    "type": "string"
                  },
                  "deviceId": {
                    "type": "string"
                  },
                  "impressionId": {
                    "description": "Impression the telemetry attaches to.",
                    "example": "imp456",
                    "type": "string"
                  },
                  "percentInView": {
                    "description": "Fraction or percentage of the ad in view. Defaults to `0`.",
                    "example": 65,
                    "format": "double",
                    "type": "number"
                  },
                  "screenId": {
                    "type": "string"
                  },
                  "viewable": {
                    "description": "Whether the ad met the viewability threshold. Defaults to\n`false`.\n",
                    "example": false,
                    "type": "boolean"
                  },
                  "viewableTime": {
                    "description": "Milliseconds the ad was viewable. Defaults to `0`. A value of\n1000 or more combined with `viewable: true` additionally\nrecords a viewable impression.\n",
                    "example": 800,
                    "type": "integer"
                  }
                },
                "required": [
                  "impressionId",
                  "adId"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "message": "Viewability metrics recorded",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "success": {
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Viewability telemetry recorded. Returned for every accepted request,\nincluding the `viewable && viewableTime >= 1000` case.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Missing required parameters"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "`impressionId` or `adId` missing. The message does not say which.\n"
          },
          "500": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Internal server error"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Unhandled server error. Nothing is guaranteed to have been written.\n\nA retry is safe: the PostgreSQL write is an idempotent `UPDATE`\nkeyed on `impressionId`, never an insert, so replaying a request\nrewrites the same row with the same values and cannot duplicate\nanything.\n"
          }
        },
        "security": [],
        "summary": "Record viewability telemetry",
        "tags": [
          "Impression Tracking"
        ],
        "x-codeSamples": [
          {
            "label": "cURL",
            "lang": "curl",
            "source": "# Open endpoint: no signature, no rate limit.\n# `adId` must be a JSON string — a number produces a 500.\ncurl -sS -X POST https://api.trillboards.com/openrtb/v1/viewability \\\n  -H 'Content-Type: application/json' \\\n  -d '{\n        \"impressionId\": \"imp456\",\n        \"adId\": \"64f000000000000000000001\",\n        \"viewable\": true,\n        \"viewableTime\": 2400,\n        \"percentInView\": 82\n      }'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/win": {
      "get": {
        "description": "Win notification (`nurl`) fired by a DSP or exchange after it wins an\nauction. Trillboards puts this URL on every bid response as\n\n```\n{API_URL}/openrtb/v1/win?price=${AUCTION_PRICE}&adid=<ad-id>&impid=<imp-id>\n```\n\nThe `${AUCTION_PRICE}` macro is emitted **literally** — Trillboards never\nexpands it. Expanding it is the caller's job, and an unexpanded macro\narrives as a non-numeric `price` and is rejected.\n\n## This endpoint does not bill\n\nIt validates the price, resolves the advertisement, checks the campaign\nbudget, and runs spend-anomaly detection whose only sink is a log line.\nNo revenue row, no `total_spent` increment, and no win flag is written.\nBilling happens on `POST /openrtb/v1/proof-of-play`, the only path that\npublishes a completed impression and fires the `burl`. Treat a `200` here\nas \"the win was accepted for validation\", never as \"the impression was\npaid for\".\n\n## Response bodies are not JSON\n\nEvery branch except the `429` answers with `res.send(<string>)`, so the\nContent-Type is `text/html; charset=utf-8` and the body is a bare\nsentence. **Branch on the status code, never on the body.**\n\n## Price handling\n\n`parseFloat`, then a single guard: the value must be `> 0` and `<= 500`.\n`parseFloat` is prefix-tolerant, so `price=1.5USD` is accepted as `1.5`.\nThere is no currency parameter on this endpoint — the price is\ninterpreted in the currency of the bid response that carried the `nurl`.\n",
        "operationId": "handleWinNotification",
        "parameters": [
          {
            "description": "Clearing price, already macro-expanded. Must parse to a number\nstrictly greater than 0 and no greater than 500.\n",
            "example": 3.5,
            "in": "query",
            "name": "price",
            "required": true,
            "schema": {
              "exclusiveMinimum": true,
              "format": "double",
              "maximum": 500,
              "minimum": 0,
              "type": "number"
            }
          },
          {
            "description": "Trillboards advertisement id, echoed from the bid response `adid`.",
            "example": "64f000000000000000000001",
            "in": "query",
            "name": "adid",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Impression id from the bid request. Required, but **presence-checked\nonly** — it is not used to resolve or credit the impression.\n",
            "example": "imp1",
            "in": "query",
            "name": "impid",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/html": {
                "example": "OK",
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Win accepted. Body is the literal string `OK`. A spend anomaly is\nlogged but does NOT change this status.\n"
          },
          "400": {
            "content": {
              "text/html": {
                "example": "Invalid win price",
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Either a required parameter was missing (`Missing required\nparameters: price, adid, impid` — emitted when ANY of the three is\nabsent, not only when all are) or the price failed the range guard\n(`Invalid win price`). An unexpanded `${AUCTION_PRICE}` lands here.\n"
          },
          "404": {
            "content": {
              "text/html": {
                "example": "Advertisement not found",
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "No advertisement matches `adid`. Body is `Advertisement not found`."
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Budget exhausted"
                },
                "schema": {
                  "properties": {
                    "error": {
                      "example": "Budget exhausted",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The campaign budget would be exceeded by this win\n(`total_spent + price > budget`). The only JSON body on this\noperation, and NOT a rate limit — do not retry it with backoff.\n"
          },
          "500": {
            "content": {
              "text/html": {
                "example": "Internal server error",
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "Unhandled server error. Body is `Internal server error`."
          }
        },
        "security": [],
        "summary": "Handle win notification",
        "tags": [
          "Programmatic Bidding"
        ],
        "x-codeSamples": [
          {
            "label": "cURL",
            "lang": "curl",
            "source": "# Validates a win. Does NOT bill — see POST /openrtb/v1/proof-of-play.\ncurl -sS -G https://api.trillboards.com/openrtb/v1/win \\\n  -d adid=64f000000000000000000001 \\\n  -d impid=imp456 \\\n  -d screenId=507f1f77bcf86cd799439022\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v1/win-notice/{token}": {
      "get": {
        "description": "The `start` tracking event a demand partner's win notice rides in. For a\npartner whose notice belongs at ad start (Playdigo), the VAST returned by\n`GET /openrtb/v1/vast/{partnerKey}/{screenId}` carries this URL as a\n`<Tracking event=\"start\">` instead of firing the partner's `nurl` when the\nbid arrives. The player calls it on the ad's first frame; the server\nanswers with the tracking pixel at once and then calls the stored `nurl`,\nonce per bid. A bid the player never starts never reaches the partner.\n\nThe `token` is a one-time key minted per bid. Unknown, expired, repeated\nand malformed tokens get the same pixel and call no one.\n",
        "operationId": "relayPartnerWinNotice",
        "parameters": [
          {
            "description": "32 lowercase hex characters, minted per bid.",
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "pattern": "^[0-9a-f]{32}$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "image/gif": {
                "example": "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "A 42-byte transparent GIF, identical for every token. The image carries\nno outcome.\n",
            "headers": {
              "Cache-Control": {
                "description": "Always `no-store, no-cache, must-revalidate, private`.",
                "schema": {
                  "example": "no-store, no-cache, must-revalidate, private",
                  "type": "string"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TrackingRateLimited"
          }
        },
        "security": [],
        "summary": "Relay a demand partner's win notice at ad start",
        "tags": [
          "Impression Tracking"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/account": {
      "get": {
        "description": "Returns the authenticated DSP's commercial profile — display/legal name,\nbilling email, payment terms, currency, tax id, and billing address — plus\nthe deals visible to the caller's seat.\n\nCommercial terms are sourced from the canonical billing-entity record\n(`dsp_billing_entities` + `dsp_ssp_contacts`), falling back to the DSP\nregistration's company name + billing email for newly-onboarded seats that\ndo not yet have a billing entity.\n\n**Scope:** requires `stats:read`. The `deals` array (with targeting such as\n`eligible_screens`) is included ONLY when the key also holds `deals:read`;\notherwise `deals` is empty and `deal_count` is 0.\n\nThe internal Stripe customer id is NEVER returned.\n",
        "operationId": "getAccount",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "commercial": {
                      "billing_address": {
                        "city": "San Francisco",
                        "country": "US",
                        "line1": "500 Market St",
                        "line2": null,
                        "postal_code": "94105",
                        "region": "CA"
                      },
                      "billing_email": "billing@amp.com",
                      "company_legal_name": "AMP Inc",
                      "currency": "USD",
                      "display_name": "Advertising Marketplace",
                      "payment_terms_days": 30,
                      "tax_id": "TAX-123"
                    },
                    "deal_count": 1,
                    "deals": [
                      {
                        "deal_id": "TB-AMP-PG-001",
                        "deal_type": "programmatic_guaranteed",
                        "eligible_countries": [
                          "US"
                        ],
                        "eligible_screens": [
                          "6512a9f3c4d5e6f708192a3b"
                        ],
                        "eligible_venue_types": [
                          "transit",
                          "retail"
                        ],
                        "end_date": "2026-12-31T00:00:00.000Z",
                        "floor_cpm": 5,
                        "start_date": "2026-01-01T00:00:00.000Z",
                        "status": "active"
                      }
                    ],
                    "seat": "amp_2026"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "commercial": {
                          "description": "Commercial profile (no internal Stripe identifiers)",
                          "properties": {
                            "billing_address": {
                              "properties": {
                                "city": {
                                  "nullable": true,
                                  "type": "string"
                                },
                                "country": {
                                  "nullable": true,
                                  "type": "string"
                                },
                                "line1": {
                                  "nullable": true,
                                  "type": "string"
                                },
                                "line2": {
                                  "nullable": true,
                                  "type": "string"
                                },
                                "postal_code": {
                                  "nullable": true,
                                  "type": "string"
                                },
                                "region": {
                                  "nullable": true,
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            },
                            "billing_email": {
                              "example": "billing@amp.com",
                              "nullable": true,
                              "type": "string"
                            },
                            "company_legal_name": {
                              "example": "AMP Inc",
                              "nullable": true,
                              "type": "string"
                            },
                            "currency": {
                              "example": "USD",
                              "nullable": true,
                              "type": "string"
                            },
                            "display_name": {
                              "example": "Advertising Marketplace",
                              "nullable": true,
                              "type": "string"
                            },
                            "payment_terms_days": {
                              "description": "Net payment terms in days",
                              "example": 30,
                              "nullable": true,
                              "type": "integer"
                            },
                            "tax_id": {
                              "description": "Tax registration id (e.g. VAT / EIN)",
                              "example": "TAX-123",
                              "nullable": true,
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "deal_count": {
                          "description": "Number of deals returned",
                          "example": 1,
                          "type": "integer"
                        },
                        "deals": {
                          "description": "Deals visible to the caller's seat. Empty unless the key\nholds `deals:read`.\n",
                          "items": {
                            "properties": {
                              "deal_id": {
                                "example": "TB-AMP-PG-001",
                                "type": "string"
                              },
                              "deal_type": {
                                "example": "programmatic_guaranteed",
                                "type": "string"
                              },
                              "eligible_countries": {
                                "items": {
                                  "type": "string"
                                },
                                "nullable": true,
                                "type": "array"
                              },
                              "eligible_screens": {
                                "description": "Screen ids this guaranteed deal targets",
                                "items": {
                                  "type": "string"
                                },
                                "nullable": true,
                                "type": "array"
                              },
                              "eligible_venue_types": {
                                "items": {
                                  "type": "string"
                                },
                                "nullable": true,
                                "type": "array"
                              },
                              "end_date": {
                                "format": "date-time",
                                "nullable": true,
                                "type": "string"
                              },
                              "floor_cpm": {
                                "description": "Reserve/guaranteed CPM in USD",
                                "example": 5,
                                "type": "number"
                              },
                              "start_date": {
                                "format": "date-time",
                                "nullable": true,
                                "type": "string"
                              },
                              "status": {
                                "example": "active",
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "seat": {
                          "description": "DSP seat identifier",
                          "example": "amp_2026",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "DSP account profile",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get DSP account profile (commercial terms + deals)",
        "tags": [
          "Reporting"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/account\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/adslots": {
      "get": {
        "description": "Returns OpenRTB 2.6 bid request objects for all active screens currently seeking\nprogrammatic demand. Each bid request includes live FEIN (Face/Edge Intelligence\nNetwork) signals in the `ext.trb.fein` extension when available.\n\n**This is the primary inventory discovery endpoint.** Use it to:\n- Browse all available screens with real-time audience data\n- Filter by geography, venue type, and audience signals\n- Get ready-to-use OpenRTB 2.6 bid request objects for your bidder\n\n**FEIN signal filtering:**\n- `min_vas=5.0` — Only screens with 5+ Verified Attention Seconds (7-day average)\n- `min_attention=60` — Only screens where 60%+ of detected faces are paying attention\n- `emotion=happy` — Only screens where the dominant audience emotion is \"happy\"\n\nFilters are combined with AND logic. Screens that lack FEIN sensors are excluded\nwhen FEIN filters are applied.\n\n**Signal coverage** is reported in the response. Not all screens have FEIN sensors:\n- `total_screens` — Total screens matching geographic/venue filters\n- `fein_enriched` — How many of those have live or historical audience data\n- `after_filters` — How many remain after applying FEIN signal filters\n",
        "operationId": "getAdSlots",
        "parameters": [
          {
            "description": "Response format. Determines the shape of the response body.\n- `screens` (default) — Returns `{ total, screens, fein_coverage }` with\n  screen summaries and FEIN sensor coverage stats.\n- `openrtb` — Returns `{ available_slots, bid_requests }` with full\n  OpenRTB 2.6 bid request objects suitable for programmatic bidding.\n",
            "example": "openrtb",
            "in": "query",
            "name": "format",
            "required": false,
            "schema": {
              "default": "screens",
              "enum": [
                "openrtb",
                "screens"
              ],
              "type": "string"
            }
          },
          {
            "description": "Filter by venue taxonomy. Common values: retail, restaurant, bar, gym,\nhotel_lobby, transit, office, hospital, salon, gas_station, laundromat,\nairport, university, coworking. Case-insensitive.\n",
            "example": "retail",
            "in": "query",
            "name": "venue_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "ISO 3166-1 alpha-2 country code. Filters screens to the specified country.\n",
            "example": "US",
            "in": "query",
            "name": "country",
            "required": false,
            "schema": {
              "maxLength": 2,
              "minLength": 2,
              "type": "string"
            }
          },
          {
            "description": "State or province code (e.g., \"CA\" for California, \"ON\" for Ontario).\nRequires `country` parameter for accurate results.\n",
            "example": "NY",
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "City name filter. Case-insensitive partial match.\n",
            "example": "New York",
            "in": "query",
            "name": "city",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Maximum number of screens to return. Default is 50. Maximum is 200.\nResults are ordered by FEIN signal quality (live > historical > none),\nthen by VAS score descending.\n",
            "example": 50,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Minimum Verified Attention Seconds (7-day rolling average). VAS measures\nhow many seconds of verified human attention a screen receives per ad play.\nHigher VAS means more engaged audiences. Typical range: 2.0 to 15.0.\n",
            "example": 5,
            "in": "query",
            "name": "min_vas",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "number"
            }
          },
          {
            "description": "Minimum attention score on a 0-100 scale. Attention score measures the\npercentage of detected faces that are looking at the screen. A score of\n60 means 60% of people facing the screen are actively looking at it.\n",
            "example": 60,
            "in": "query",
            "name": "min_attention",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 0,
              "type": "number"
            }
          },
          {
            "description": "Filter by dominant audience emotion detected by on-device face analysis.\nReturns only screens where the current dominant emotion matches.\n",
            "example": "happy",
            "in": "query",
            "name": "emotion",
            "required": false,
            "schema": {
              "enum": [
                "happy",
                "sad",
                "angry",
                "surprised",
                "neutral",
                "fear",
                "disgust"
              ],
              "type": "string"
            }
          },
          {
            "description": "Opaque pagination cursor. Pass the `cursor.next` value from a\nprevious response to fetch the subsequent page. Cursors encode\nthe (updated_at, id) tuple of the last screen returned, so\npagination remains stable across concurrent inventory updates.\nOmit on the first request to start from the beginning.\n",
            "example": "eyJsYXN0X3VwZGF0ZWRfYXQiOiIyMDI2LTA1LTAxVDAwOjAwOjAwLjAwMFoiLCJsYXN0X2lkIjoxMjN9",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Delta-sync watermark. ISO8601 timestamp; only screens whose\n`updated_at >= since` are returned. Use this for hourly cron\npolling — pass the timestamp of the last successful sync to\nfetch only screens that changed since then.\n",
            "example": "2026-05-11T00:00:00Z",
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "When `true`, the response includes `total_count` — the total number\nof inventory rows matching your filters. Use it to plan a batched\nfull sync. `total_count` is also returned automatically on a\nfirst-sync page (no `cursor`); pass `include_count=true` to obtain\nit on a cursored page as well. Defaults to `false`.\n",
            "example": true,
            "in": "query",
            "name": "include_count",
            "required": false,
            "schema": {
              "default": false,
              "type": "boolean"
            }
          },
          {
            "description": "Inventory source partition:\n\n- `owned` (default) — Trillboards owned + operated screens only. Byte-for-byte\n  the historical behavior.\n- `vistar` — curated resold partner (Vistar Media) inventory only.\n- `all` — owned inventory first, then the curated partner partition.\n\nPartner inventory is surfaced only when the server-side `VISTAR_BOOKABLE_DISPLAY`\nflag is enabled (gated on the buying/fulfillment track shipping, so displayed\ninventory is always transactable). When it is off, `all` returns owned inventory\nonly and `vistar` returns an empty page (the partner partition is forced empty).\n\nEvery screen row carries a top-level `source` discriminator (`owned` | `vistar`),\nand the response includes `sources_enabled` — the list of active partitions.\nPagination cursors are source-scoped; legacy 2-part cursors decode as `owned`,\nso existing integrations are unaffected.\n",
            "example": "all",
            "in": "query",
            "name": "source",
            "required": false,
            "schema": {
              "default": "owned",
              "enum": [
                "owned",
                "vistar",
                "all"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "available_slots": 47,
                  "bid_requests": [
                    {
                      "device": {
                        "devicetype": 3,
                        "geo": {
                          "city": "New York",
                          "country": "USA",
                          "lat": 40.7589,
                          "lon": -73.9851,
                          "region": "NY",
                          "type": 1,
                          "zip": "10036"
                        },
                        "ua": "Mozilla/5.0 (Linux; Android 12; SHIELD Android TV)"
                      },
                      "ext": {
                        "trb": {
                          "fein": {
                            "ad_receptivity": 0.82,
                            "attention_score": 0.73,
                            "crowd_density": 45,
                            "data_quality": "live",
                            "dominant_emotion": "happy",
                            "dwell_time_ms": 47000,
                            "income_level": "high",
                            "last_updated": "2026-04-03T14:22:10.000Z",
                            "live_face_count": 12,
                            "purchase_intent": "high",
                            "vas_7d": 8.4
                          },
                          "screen_id": "scr_7f8a9b0c1d2e3f4a",
                          "screen_name": "Times Square Deli - Counter Display",
                          "venue_type": "restaurant"
                        }
                      },
                      "id": "br_8f3a2c1e-9d47-4b6a-a1c3-2f8e5d7b9a04",
                      "imp": [
                        {
                          "bidfloor": 2.5,
                          "bidfloorcur": "USD",
                          "id": "imp_001",
                          "video": {
                            "h": 1080,
                            "maxduration": 30,
                            "mimes": [
                              "video/mp4",
                              "video/webm"
                            ],
                            "minduration": 5,
                            "protocols": [
                              2,
                              3,
                              7
                            ],
                            "w": 1920
                          }
                        }
                      ],
                      "site": {
                        "domain": "trillboards.com",
                        "id": "pub_trillboards",
                        "name": "Trillboards DOOH Network",
                        "publisher": {
                          "id": "pub_trillboards",
                          "name": "Trillboards Inc."
                        }
                      }
                    }
                  ],
                  "cur": "USD",
                  "filters_applied": {
                    "city": null,
                    "country": "US",
                    "emotion": null,
                    "min_attention": null,
                    "min_vas": 5,
                    "state": null,
                    "venue_type": "retail"
                  },
                  "signal_coverage": {
                    "after_filters": 47,
                    "fein_enriched": 89,
                    "total_screens": 142
                  },
                  "tmax": 500
                },
                "schema": {
                  "oneOf": [
                    {
                      "description": "Returned when `format=screens` (default). Provides screen summaries with FEIN sensor coverage statistics. Each screen object additionally surfaces `image_url`, `operating_hours`, `slot_duration_seconds`, and `updated_at` at the top level for AMP-style product catalog ingestion.\n",
                      "properties": {
                        "cursor": {
                          "description": "Keyset pagination cursor. When `has_more` is true,\npass `next` back as `?cursor=<value>` to fetch the\nfollowing page. When the page is terminal (fewer\nrows than the requested `limit`), `next` is `null`.\n",
                          "properties": {
                            "has_more": {
                              "description": "`true` if at least `limit` rows were returned\nfrom PostgreSQL (more pages may exist);\n`false` when the result set is exhausted.\n",
                              "example": false,
                              "type": "boolean"
                            },
                            "next": {
                              "description": "Opaque base64 cursor for the next page, or\n`null` if this is the last page. Encodes the\n(updated_at, id) tuple of the last row.\n",
                              "example": "eyJsYXN0X3VwZGF0ZWRfYXQiOiIyMDI2LTA1LTAxVDAwOjAwOjAwLjAwMFoiLCJsYXN0X2lkIjoxMjN9",
                              "nullable": true,
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "fein_coverage": {
                          "description": "FEIN sensor coverage across matching screens",
                          "properties": {
                            "screens_with_fein": {
                              "description": "Number of screens with FEIN sensors",
                              "example": 89,
                              "type": "integer"
                            },
                            "screens_without_fein": {
                              "description": "Number of screens without FEIN sensors",
                              "example": 53,
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        },
                        "screens": {
                          "description": "Array of screen summaries. Each item includes the\nstandard ScreenSummary fields plus the AMP DSP-feed\nfields surfaced for inventory cron ingestion:\n- `image_url` — Optional CDN-hosted preview/thumbnail URL.\n- `operating_hours` — Optional JSON describing\n  screen operating windows (shape is partner-defined).\n- `slot_duration_seconds` — Default slot length in\n  seconds. Defaults to 15s when not set.\n- `updated_at` — ISO8601 timestamp of the last\n  screen mutation; use as the `since` watermark on\n  the next sync.\n",
                          "items": {
                            "$ref": "#/components/schemas/ScreenSummary"
                          },
                          "type": "array"
                        },
                        "sources_enabled": {
                          "description": "The inventory source partitions active for this response:\n`[\"owned\"]` by default, or `[\"owned\",\"vistar\"]` when\npartner display is enabled (VISTAR_BOOKABLE_DISPLAY). Drives\nthe portal's partner-inventory section.\n",
                          "example": [
                            "owned"
                          ],
                          "items": {
                            "enum": [
                              "owned",
                              "vistar"
                            ],
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "total": {
                          "description": "Number of screens on this page",
                          "example": 50,
                          "type": "integer"
                        },
                        "total_count": {
                          "description": "Total inventory rows matching your filters (not just\nthis page). Returned on a first-sync page (no `cursor`)\nor when `include_count=true` — use it to plan a batched\nfull sync. On a cursored page (with `include_count=true`)\nit reflects rows from the current cursor position onward.\nAbsent when not requested.\n\nNote: this counts rows matched by the server-side\n(SQL) filters. It is omitted when the request also uses\nclient-side refinements that are applied per page\n(`max_age_rating`, `allow_alcohol`, `allow_gambling`,\n`orientation`, `has_fein`), because a catalog-level\ncount would over-report the delivered set in those\ncases. It is therefore always exact when present.\n",
                          "example": 1284,
                          "nullable": true,
                          "type": "integer"
                        }
                      },
                      "required": [
                        "total",
                        "screens",
                        "cursor",
                        "fein_coverage"
                      ],
                      "title": "Default format (format=screens)",
                      "type": "object"
                    },
                    {
                      "description": "Returned when `format=openrtb`. Provides full OpenRTB 2.6 bid request objects suitable for programmatic bidding.\n",
                      "properties": {
                        "available_slots": {
                          "description": "Total number of ad slots returned in this response",
                          "example": 47,
                          "type": "integer"
                        },
                        "bid_requests": {
                          "description": "Standard OpenRTB 2.6 bid request objects. Each represents one screen\nwith one or more impression opportunities. FEIN signals are in\n`ext.trb.fein` on each bid request.\n",
                          "items": {
                            "description": "OpenRTB 2.6 BidRequest with Trillboards FEIN extension",
                            "properties": {
                              "device": {
                                "description": "Device (screen) information",
                                "properties": {
                                  "devicetype": {
                                    "description": "OpenRTB device type.\n3=Connected TV, 6=Connected Device, 7=Set Top Box\n",
                                    "example": 3,
                                    "type": "integer"
                                  },
                                  "geo": {
                                    "description": "Geographic location of the screen",
                                    "properties": {
                                      "city": {
                                        "example": "New York",
                                        "type": "string"
                                      },
                                      "country": {
                                        "description": "ISO 3166-1 alpha-3 country code",
                                        "example": "USA",
                                        "type": "string"
                                      },
                                      "lat": {
                                        "description": "Latitude",
                                        "example": 40.7589,
                                        "type": "number"
                                      },
                                      "lon": {
                                        "description": "Longitude",
                                        "example": -73.9851,
                                        "type": "number"
                                      },
                                      "region": {
                                        "description": "State or region code",
                                        "example": "NY",
                                        "type": "string"
                                      },
                                      "type": {
                                        "description": "Location type (1=GPS, 2=IP, 3=user-provided)",
                                        "example": 1,
                                        "type": "integer"
                                      },
                                      "zip": {
                                        "example": "10036",
                                        "type": "string"
                                      }
                                    },
                                    "type": "object"
                                  },
                                  "ifa": {
                                    "description": "Device advertising ID (if available)",
                                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
                                    "type": "string"
                                  },
                                  "ua": {
                                    "description": "User agent string of the screen device",
                                    "example": "Mozilla/5.0 (Linux; Android 12; SHIELD Android TV)",
                                    "type": "string"
                                  }
                                },
                                "type": "object"
                              },
                              "ext": {
                                "description": "Trillboards-specific extensions",
                                "properties": {
                                  "trb": {
                                    "description": "Trillboards extension namespace",
                                    "properties": {
                                      "fein": {
                                        "description": "FEIN (Face/Edge Intelligence Network) real-time audience signals.\nThese signals come from on-device edge AI and are updated every\n10-30 seconds for screens with FEIN sensors.\n",
                                        "properties": {
                                          "ad_receptivity": {
                                            "description": "Predicted ad receptivity score combining attention, emotion,\nand dwell time into a single 0.0-1.0 score. Higher means\nthe audience is more likely to engage with advertising.\n",
                                            "example": 0.82,
                                            "maximum": 1,
                                            "minimum": 0,
                                            "nullable": true,
                                            "type": "number"
                                          },
                                          "attention_score": {
                                            "description": "Percentage of detected faces actively looking at the screen,\nnormalized to 0.0-1.0 range. A score of 0.73 means 73% of\npeople facing the screen are paying attention.\n",
                                            "example": 0.73,
                                            "maximum": 1,
                                            "minimum": 0,
                                            "type": "number"
                                          },
                                          "crowd_density": {
                                            "description": "Estimated total venue occupancy (not just faces at screen)",
                                            "example": 45,
                                            "nullable": true,
                                            "type": "integer"
                                          },
                                          "data_quality": {
                                            "description": "Indicates the freshness and source of FEIN data.\n- live: real-time data from on-device sensors (updated every 10-30s)\n- historical: only VAS historical averages available (hourly)\n- none: no audience data for this screen\n",
                                            "enum": [
                                              "live",
                                              "historical",
                                              "none"
                                            ],
                                            "example": "live",
                                            "type": "string"
                                          },
                                          "dominant_emotion": {
                                            "description": "Most common emotion detected among visible faces",
                                            "enum": [
                                              "happy",
                                              "sad",
                                              "angry",
                                              "surprised",
                                              "neutral",
                                              "fear",
                                              "disgust"
                                            ],
                                            "example": "happy",
                                            "nullable": true,
                                            "type": "string"
                                          },
                                          "dwell_time_ms": {
                                            "description": "Average audience dwell time in milliseconds. Measures how\nlong people stay within view of the screen.\n",
                                            "example": 47000,
                                            "type": "integer"
                                          },
                                          "income_level": {
                                            "description": "Inferred income bracket based on venue type, location,\nand audience demographics. Updated hourly.\n",
                                            "enum": [
                                              "low",
                                              "medium",
                                              "high",
                                              "premium"
                                            ],
                                            "example": "high",
                                            "nullable": true,
                                            "type": "string"
                                          },
                                          "last_updated": {
                                            "description": "Timestamp of last FEIN signal update",
                                            "example": "2026-04-03T14:22:10.000Z",
                                            "format": "date-time",
                                            "nullable": true,
                                            "type": "string"
                                          },
                                          "live_face_count": {
                                            "description": "Number of people currently facing the screen",
                                            "example": 12,
                                            "type": "integer"
                                          },
                                          "purchase_intent": {
                                            "description": "Speech-derived purchase intent level from ambient audio analysis.\nIndicates whether nearby conversations suggest buying intent.\n",
                                            "example": "high",
                                            "nullable": true,
                                            "type": "string"
                                          },
                                          "vas_7d": {
                                            "description": "Verified Attention Seconds — 7-day weighted rolling average.\nMeasures how many seconds of verified human attention the screen\nreceives per ad play. Higher is better. Industry average: 3-5s.\n",
                                            "example": 8.4,
                                            "nullable": true,
                                            "type": "number"
                                          }
                                        },
                                        "type": "object"
                                      },
                                      "screen_id": {
                                        "description": "Trillboards screen identifier",
                                        "example": "scr_7f8a9b0c1d2e3f4a",
                                        "type": "string"
                                      },
                                      "screen_name": {
                                        "description": "Human-readable screen name",
                                        "example": "Times Square Deli - Counter Display",
                                        "type": "string"
                                      },
                                      "venue_type": {
                                        "description": "Venue classification",
                                        "example": "restaurant",
                                        "type": "string"
                                      }
                                    },
                                    "type": "object"
                                  }
                                },
                                "type": "object"
                              },
                              "id": {
                                "description": "Unique bid request ID",
                                "example": "br_8f3a2c1e-9d47-4b6a-a1c3-2f8e5d7b9a04",
                                "type": "string"
                              },
                              "imp": {
                                "description": "Array of impression opportunities on this screen",
                                "items": {
                                  "properties": {
                                    "banner": {
                                      "description": "Banner impression object (for static/image ads)",
                                      "properties": {
                                        "h": {
                                          "description": "Height in pixels",
                                          "example": 1080,
                                          "type": "integer"
                                        },
                                        "w": {
                                          "description": "Width in pixels",
                                          "example": 1920,
                                          "type": "integer"
                                        }
                                      },
                                      "type": "object"
                                    },
                                    "bidfloor": {
                                      "description": "Minimum CPM bid price in USD",
                                      "example": 2.5,
                                      "type": "number"
                                    },
                                    "bidfloorcur": {
                                      "description": "Currency of the bid floor",
                                      "example": "USD",
                                      "type": "string"
                                    },
                                    "id": {
                                      "description": "Impression ID — use this as `impid` in your bid",
                                      "example": "imp_001",
                                      "type": "string"
                                    },
                                    "video": {
                                      "description": "Video impression object (for video/VAST ads)",
                                      "properties": {
                                        "h": {
                                          "example": 1080,
                                          "type": "integer"
                                        },
                                        "maxduration": {
                                          "description": "Maximum video duration in seconds",
                                          "example": 30,
                                          "type": "integer"
                                        },
                                        "mimes": {
                                          "description": "Supported MIME types",
                                          "example": [
                                            "video/mp4",
                                            "video/webm"
                                          ],
                                          "items": {
                                            "type": "string"
                                          },
                                          "type": "array"
                                        },
                                        "minduration": {
                                          "description": "Minimum video duration in seconds",
                                          "example": 5,
                                          "type": "integer"
                                        },
                                        "protocols": {
                                          "description": "Supported video protocols.\n2=VAST 2.0, 3=VAST 3.0, 5=VAST 2.0 Wrapper,\n6=VAST 3.0 Wrapper, 7=VAST 4.0, 8=VAST 4.1\n",
                                          "example": [
                                            2,
                                            3,
                                            7
                                          ],
                                          "items": {
                                            "type": "integer"
                                          },
                                          "type": "array"
                                        },
                                        "w": {
                                          "example": 1920,
                                          "type": "integer"
                                        }
                                      },
                                      "type": "object"
                                    }
                                  },
                                  "type": "object"
                                },
                                "type": "array"
                              },
                              "site": {
                                "description": "Publisher site/app information",
                                "properties": {
                                  "domain": {
                                    "example": "trillboards.com",
                                    "type": "string"
                                  },
                                  "id": {
                                    "description": "Trillboards publisher ID",
                                    "example": "pub_trillboards",
                                    "type": "string"
                                  },
                                  "name": {
                                    "example": "Trillboards DOOH Network",
                                    "type": "string"
                                  },
                                  "publisher": {
                                    "properties": {
                                      "id": {
                                        "example": "pub_trillboards",
                                        "type": "string"
                                      },
                                      "name": {
                                        "example": "Trillboards Inc.",
                                        "type": "string"
                                      }
                                    },
                                    "type": "object"
                                  }
                                },
                                "type": "object"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "cur": {
                          "description": "Currency for bid floor prices (always USD)",
                          "example": "USD",
                          "type": "string"
                        },
                        "filters_applied": {
                          "description": "Echo of the filters that were applied to this request",
                          "properties": {
                            "city": {
                              "example": "New York",
                              "nullable": true,
                              "type": "string"
                            },
                            "country": {
                              "description": "Country filter that was applied",
                              "example": "US",
                              "nullable": true,
                              "type": "string"
                            },
                            "emotion": {
                              "example": "happy",
                              "nullable": true,
                              "type": "string"
                            },
                            "min_attention": {
                              "example": 60,
                              "nullable": true,
                              "type": "number"
                            },
                            "min_vas": {
                              "example": 5,
                              "nullable": true,
                              "type": "number"
                            },
                            "state": {
                              "example": "NY",
                              "nullable": true,
                              "type": "string"
                            },
                            "venue_type": {
                              "description": "Venue type filter that was applied",
                              "example": "retail",
                              "nullable": true,
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "signal_coverage": {
                          "description": "Breakdown of FEIN signal availability across matching screens.\nUseful for understanding how much of your target inventory has\naudience data.\n",
                          "properties": {
                            "after_filters": {
                              "description": "Screens remaining after applying FEIN signal filters",
                              "example": 47,
                              "type": "integer"
                            },
                            "fein_enriched": {
                              "description": "Screens with live or historical FEIN audience data",
                              "example": 89,
                              "type": "integer"
                            },
                            "total_screens": {
                              "description": "Total screens matching geographic and venue filters",
                              "example": 142,
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        },
                        "tmax": {
                          "description": "Maximum time in milliseconds to submit a bid response",
                          "example": 500,
                          "type": "integer"
                        }
                      },
                      "required": [
                        "available_slots",
                        "bid_requests",
                        "cur",
                        "tmax"
                      ],
                      "title": "OpenRTB format (format=openrtb)",
                      "type": "object"
                    }
                  ]
                }
              }
            },
            "description": "Available inventory. Response shape depends on the `format` query parameter:\n\n- **Default (`format=screens`):** Returns `{ total, screens, fein_coverage }`\n  with screen summaries and FEIN sensor coverage statistics.\n- **OpenRTB (`format=openrtb`):** Returns `{ available_slots, bid_requests }`\n  with full OpenRTB 2.6 BidRequest objects extended with `ext.trb.fein` for\n  audience signals.\n\nEvery screen row carries a top-level `source` discriminator (`owned` | `vistar`),\nand the response includes `sources_enabled` — the active inventory partitions\n(e.g. `[\"owned\"]`, or `[\"owned\",\"vistar\"]` when partner display is enabled). See\nthe `source` query parameter.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 200,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 199,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Browse available inventory with FEIN signals",
        "tags": [
          "Inventory"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/adslots\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/analytics/funnel": {
      "get": {
        "description": "Returns a VAST event funnel: `requests → bids_received → renders_started →\nplays_completed`, plus computed rates (`bid_rate`, `fill_rate`,\n`start_to_complete`), a top-50 per-screen breakdown, and optional timeseries\nat daily/hourly granularity.\n\n**Data source:** `openrtb_vast_screen_hourly` PG rollup table (OLTP-safe,\nrefreshed every 5 minutes from ClickHouse). In-memory cache: 5 minutes per\nquery fingerprint.\n\n**Funnel column mapping:**\n- `request_count` → `requests`\n- `response_count` → `bids_received`\n- `filled_count` → `renders_started` (ad began playing)\n- `completed_count` → `plays_completed` (ad played to full completion)\n\n**Rate limit category:** Reporting (100 req/min).\n",
        "operationId": "getVastFunnel",
        "parameters": [
          {
            "description": "Flight start date (YYYY-MM-DD).",
            "example": "2026-05-01",
            "in": "query",
            "name": "start_date",
            "required": true,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Flight end date (YYYY-MM-DD). Inclusive.",
            "example": "2026-05-14",
            "in": "query",
            "name": "end_date",
            "required": true,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "description": "Comma-separated list of screen IDs to restrict the funnel to.",
            "example": "65a1b2c3d4e5f6a7b8c9d0e1,65a1b2c3d4e5f6a7b8c9d0e2",
            "in": "query",
            "name": "screen_ids",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Reserved for future use (creative-level funnel filter).",
            "in": "query",
            "name": "creative_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Timeseries granularity:\n- `flight` — no timeseries, only aggregate + per-screen (default)\n- `daily` — one bucket per day\n- `hourly` — one bucket per hour\n",
            "example": "daily",
            "in": "query",
            "name": "granularity",
            "required": false,
            "schema": {
              "default": "flight",
              "enum": [
                "flight",
                "daily",
                "hourly"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "by_screen": [
                      {
                        "completion_rate": 0.928,
                        "completions": 1180,
                        "fill_rate": 0.876,
                        "requests": 1450,
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e1"
                      },
                      {
                        "completion_rate": 0.919,
                        "completions": 1105,
                        "fill_rate": 0.862,
                        "requests": 1380,
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e2"
                      }
                    ],
                    "funnel": {
                      "bid_rate": 0.921,
                      "bids_received": 26210,
                      "errors": 420,
                      "fill_rate": 0.875,
                      "no_bids": 1820,
                      "plays_completed": 22140,
                      "renders_started": 24890,
                      "requests": 28450,
                      "start_to_complete": 0.889
                    },
                    "timeseries": [
                      {
                        "completions": 2340,
                        "fill_rate": 0.864,
                        "period": "2026-05-01T00:00:00.000Z",
                        "requests": 2850
                      },
                      {
                        "completions": 2410,
                        "fill_rate": 0.872,
                        "period": "2026-05-02T00:00:00.000Z",
                        "requests": 2910
                      }
                    ]
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "by_screen": {
                          "items": {
                            "properties": {
                              "completion_rate": {
                                "example": 0.928,
                                "type": "number"
                              },
                              "completions": {
                                "example": 1180,
                                "type": "integer"
                              },
                              "fill_rate": {
                                "example": 0.876,
                                "type": "number"
                              },
                              "requests": {
                                "example": 1450,
                                "type": "integer"
                              },
                              "screen_id": {
                                "example": "65a1b2c3d4e5f6a7b8c9d0e1",
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "funnel": {
                          "$ref": "#/components/schemas/FunnelStats"
                        },
                        "timeseries": {
                          "items": {
                            "properties": {
                              "completions": {
                                "example": 2340,
                                "type": "integer"
                              },
                              "fill_rate": {
                                "example": 0.864,
                                "type": "number"
                              },
                              "period": {
                                "example": "2026-05-01T00:00:00.000Z",
                                "format": "date-time",
                                "type": "string"
                              },
                              "requests": {
                                "example": 2850,
                                "type": "integer"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Funnel, per-screen breakdown, and optional timeseries.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "start_date and end_date are required (YYYY-MM-DD)",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request. Common causes: missing `start_date`/`end_date`, invalid\ndate format (must be YYYY-MM-DD), or `start_date` after `end_date`.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "VAST event funnel (requests → renders → completions)",
        "tags": [
          "Analytics"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/analytics/funnel\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/audience/lookalike/{screenId}": {
      "get": {
        "description": "Returns screens whose observed audience profile most resembles the\nsource screen's, for prospecting comparable inventory.\n\n## Authentication\n\nThis is a DSP surface, not a portal one. It takes the same\n**platform API key** as `GET /openrtb/v2/adslots` — as\n`Authorization: Bearer <platform_api_key>` or an `x-api-key` header —\nand the key must hold the `inventory:read` scope, which every onboarded\nDSP key carries. A portal user's JWT is not accepted here. The route\nshares the DSP inventory rate-limit budget.\n",
        "operationId": "findLookalikeScreens",
        "parameters": [
          {
            "$ref": "#/components/parameters/ScreenIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "matches": [
                    {
                      "last_seen": "2026-08-30T21:04:11.000Z",
                      "metadata": {
                        "attention": 0.62,
                        "face_count": 3.4,
                        "income": "medium",
                        "lifestyle": "urban_commuter",
                        "outcome_cpm_lift": 0.08,
                        "timestamp": "2026-08-30T21:04:11.000Z"
                      },
                      "screen_id": "507f1f77bcf86cd799439055",
                      "similarity": 0.912
                    },
                    {
                      "last_seen": "2026-08-30T19:41:02.000Z",
                      "metadata": {
                        "attention": 0.58,
                        "face_count": 2.9,
                        "income": "medium",
                        "lifestyle": "urban_commuter",
                        "outcome_cpm_lift": 0.02,
                        "timestamp": "2026-08-30T19:41:02.000Z"
                      },
                      "screen_id": "507f1f77bcf86cd799439078",
                      "similarity": 0.874
                    }
                  ],
                  "matches_count": 2,
                  "source_screen_id": "507f1f77bcf86cd799439022"
                },
                "schema": {
                  "properties": {
                    "matches": {
                      "items": {
                        "properties": {
                          "last_seen": {
                            "description": "When the matched vector was stored.",
                            "format": "date-time",
                            "type": "string"
                          },
                          "metadata": {
                            "description": "Context snapshot captured with the vector. Keys are\nbest-effort and may be absent or null.\n",
                            "properties": {
                              "attention": {
                                "type": "number"
                              },
                              "face_count": {
                                "type": "number"
                              },
                              "income": {
                                "type": "string"
                              },
                              "lifestyle": {
                                "type": "string"
                              },
                              "outcome_cpm_lift": {
                                "type": "number"
                              },
                              "timestamp": {
                                "format": "date-time",
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "screen_id": {
                            "type": "string"
                          },
                          "similarity": {
                            "description": "Cosine similarity in `0..1`, three decimals.",
                            "format": "double",
                            "type": "number"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "matches_count": {
                      "description": "Length of `matches`.",
                      "type": "integer"
                    },
                    "source_screen_id": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Matching screens, closest first.\n\n**An empty list is the answer to several different questions.** No\nstored vector for the source screen, no screen clearing\n`min_similarity`, an empty permitted-inventory set, and a backend\nfailure all return `200` with `matches_count: 0`. This operation\nnever distinguishes them, so do not read an empty result as \"this\nscreen has no lookalikes\".\n\n`similarity` is cosine similarity in `0..1`, rounded to three\ndecimals. It orders the list; it is not a probability and is not\ncomparable across source screens.\n"
          }
        },
        "security": [
          {
            "dspBearerAuth": []
          },
          {
            "dspApiKeyAuth": []
          }
        ],
        "summary": "Find lookalike screens",
        "tags": [
          "Audience Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/audience/predict/{screenId}": {
      "get": {
        "description": "Projects the audience a screen is likely to have in a given hour-of-day\nand day-of-week slot, from its own history over `lookback_days`.\n\nA screen with too little history is **not** an error: the call returns\n`200` with `prediction: null` and a `message` saying so. Branch on\n`prediction`, never on the status code.\n\n## Authentication\n\nThis is a DSP surface, not a portal one. It takes the same\n**platform API key** as `GET /openrtb/v2/adslots` — as\n`Authorization: Bearer <platform_api_key>` or an `x-api-key` header —\nand the key must hold the `inventory:read` scope, which every onboarded\nDSP key carries. A portal user's JWT is not accepted here. The route\nshares the DSP inventory rate-limit budget.\n",
        "operationId": "predictAudience",
        "parameters": [
          {
            "$ref": "#/components/parameters/ScreenIdPath"
          },
          {
            "in": "query",
            "name": "hour",
            "required": true,
            "schema": {
              "maximum": 23,
              "minimum": 0,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "day",
            "required": true,
            "schema": {
              "maximum": 6,
              "minimum": 0,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "lookback_days",
            "schema": {
              "default": 30,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "insufficient": {
                    "summary": "Not enough history — note the missing keys",
                    "value": {
                      "message": "Insufficient data for prediction",
                      "prediction": null,
                      "screen_id": "507f1f77bcf86cd799439022"
                    }
                  },
                  "predicted": {
                    "summary": "Enough history to predict",
                    "value": {
                      "day_of_week": 4,
                      "hour_utc": 18,
                      "prediction": {
                        "confidence": 1,
                        "predicted_attention": 0.62,
                        "predicted_face_count": 3.4,
                        "sample_count": 214,
                        "typical_income": "medium",
                        "typical_lifestyle": "urban_commuter"
                      },
                      "screen_id": "507f1f77bcf86cd799439022"
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "day_of_week": {
                      "description": "Omitted when `prediction` is `null`.",
                      "type": "integer"
                    },
                    "hour_utc": {
                      "description": "Omitted when `prediction` is `null`.",
                      "type": "integer"
                    },
                    "message": {
                      "description": "Present only when `prediction` is `null`.",
                      "type": "string"
                    },
                    "prediction": {
                      "description": "`null` when there is insufficient history.",
                      "nullable": true,
                      "properties": {
                        "confidence": {
                          "description": "`min(sample_count / 30, 1)`. Coverage, not accuracy.\n",
                          "format": "double",
                          "type": "number"
                        },
                        "predicted_attention": {
                          "description": "`0..1`, two decimal places.",
                          "format": "double",
                          "type": "number"
                        },
                        "predicted_face_count": {
                          "description": "One decimal place.",
                          "format": "double",
                          "type": "number"
                        },
                        "sample_count": {
                          "description": "Observations behind the estimate.",
                          "type": "integer"
                        },
                        "typical_income": {
                          "type": "string"
                        },
                        "typical_lifestyle": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "screen_id": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "The prediction, or an explicit statement that there is not enough\nhistory to make one.\n\n**The two bodies do not have the same keys.** When `prediction` is\n`null`, `hour_utc` and `day_of_week` are omitted entirely and a\n`message` is added. Read `prediction` first.\n\n`confidence` is `sample_count / 30`, capped at `1` — it is a measure\nof how much history backs the estimate, not of how accurate the\nestimate is. A screen with 30 observations reports `1`.\n"
          }
        },
        "security": [
          {
            "dspBearerAuth": []
          },
          {
            "dspApiKeyAuth": []
          }
        ],
        "summary": "Predict audience for a screen and time window",
        "tags": [
          "Audience Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/audience/rank-creatives": {
      "post": {
        "description": "Ranks a candidate set of creatives by how closely each one's historical\nplay context resembles the audience you describe. Use it to pick which\ncreative to bid on a given slot with.\n\nScoring is vector similarity over observed audience contexts, not a\nmodelled conversion or revenue estimate. `predicted_score` is a cosine\nsimilarity in `0..1` — a relative ordering signal, comparable **within**\none response and not across calls or across audiences.\n\n## Sparse and failed results both return 200 with an empty list\n\nA creative with no recorded play context is simply absent from the\nresult rather than scored zero, so `ranked_count` is routinely smaller\nthan the number of ids you sent. A backend failure is also reported as an\nempty list under a `200`. An empty `creatives` array therefore means\n\"no ranking available\", never \"these creatives are a poor match\" —\nfall back to your own ordering rather than dropping the creatives.\n\n`historical_plays` is the evidence count behind each score. Treat a score\nbacked by a handful of plays as weak.\n\nThis route requires the same DSP platform key as the inventory routes,\nholding the `inventory:read` scope, and shares their 200-per-minute\nbudget.\n",
        "operationId": "rankCreativesForAudience",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "audience": {
                  "adReceptivityScore": 0.68,
                  "daypart": "evening",
                  "dominantEmotion": "happy",
                  "estimatedOccupancy": 45,
                  "incomeLevel": "medium",
                  "speechPurchaseIntent": "moderate"
                },
                "creative_ids": [
                  "5f2a1b3c4d5e6f7a8b9c0d1e",
                  "6a1b2c3d4e5f60718293a4b5",
                  "7b2c3d4e5f60718293a4b5c6"
                ],
                "limit": 5
              },
              "schema": {
                "properties": {
                  "audience": {
                    "description": "Audience context to rank against — the platform's live\nsignal blob for a screen. Every key is optional, unrecognised\nkeys are ignored, and missing signals are treated as zero, so\na misspelled or sparse audience degrades the ranking silently\nrather than erroring. There is no validation to tell you that\nhappened; compare `historical_plays` against what you expected\nif a ranking looks arbitrary.\n\nStable keys worth sending: `dominantEmotion`, `incomeLevel`,\n`estimatedOccupancy`, `adReceptivityScore`,\n`emotionalEngagementScore`, `speechPurchaseIntent`,\n`inferredVenueType`, `venueSubtype`, `daypart`,\n`passerbyRatio`, `queueLength`, `vehicleCount`,\n`objectDiversityScore`, `temperatureF`, `isRaining`,\n`ambientLux`, `noiseTier`, `topLifestyleSegment`,\n`ageBucketDistribution`, `incomeTierDistribution`.\n\nThe vectoriser also accepts several windowed pre-aggregate\nsignal names that the platform is retiring under its\nprimitive-signal contract. They still work, they are\ndeliberately not listed here, and a new integration should\nnot be built on them.\n\nNote that these names are **not** the ones\n`GET /openrtb/v2/adslots` publishes in its `fein` block —\nthat surface renames them (`attention_score`,\n`live_face_count`, `crowd_density`, `dominant_emotion`).\nThe two vocabularies do not round-trip, so map deliberately\nrather than passing an adslots `fein` object straight through.\n",
                    "example": {
                      "adReceptivityScore": 0.68,
                      "daypart": "evening",
                      "dominantEmotion": "happy",
                      "estimatedOccupancy": 45,
                      "incomeLevel": "medium",
                      "speechPurchaseIntent": "moderate"
                    },
                    "type": "object"
                  },
                  "creative_ids": {
                    "description": "Candidate creatives to rank. Must be non-empty — an empty\narray is a `400`, not an empty result.\n",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "type": "array"
                  },
                  "limit": {
                    "default": 5,
                    "description": "Maximum creatives to return, highest score first. Defaults to\n5, capped at 50.\n",
                    "example": 5,
                    "maximum": 50,
                    "type": "integer"
                  }
                },
                "required": [
                  "audience",
                  "creative_ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "creatives": [
                    {
                      "creative_id": "6a1b2c3d4e5f60718293a4b5",
                      "historical_plays": 1847,
                      "predicted_score": 0.912
                    },
                    {
                      "creative_id": "5f2a1b3c4d5e6f7a8b9c0d1e",
                      "historical_plays": 932,
                      "predicted_score": 0.874
                    },
                    {
                      "creative_id": "7b2c3d4e5f60718293a4b5c6",
                      "historical_plays": 41,
                      "predicted_score": 0.661
                    }
                  ],
                  "ranked_count": 3
                },
                "schema": {
                  "properties": {
                    "creatives": {
                      "items": {
                        "properties": {
                          "creative_id": {
                            "type": "string"
                          },
                          "historical_plays": {
                            "description": "Plays behind the score. Low counts mean a weak\nestimate.\n",
                            "type": "integer"
                          },
                          "predicted_score": {
                            "description": "Cosine similarity in 0..1, three decimal places.\nOrdering signal only — not a conversion or revenue\nprediction, and not comparable across responses.\n",
                            "format": "double",
                            "type": "number"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "ranked_count": {
                      "description": "Length of `creatives`. Often fewer than the ids you sent.\n",
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Ranking, best match first. Also returned — with an empty list — when\nno candidate has usable history or the backend was unavailable.\n"
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": {
                    "code": "validation_error",
                    "doc_url": "https://api.trillboards.com/docs/errors#validation_error",
                    "message": "Missing required body: audience object and creative_ids array.",
                    "param": "audience",
                    "request_id": "req_7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c",
                    "type": "invalid_request_error"
                  }
                },
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorEnvelope"
                }
              }
            },
            "description": "`audience` is missing, or `creative_ids` is missing or empty. Note\nthat `param` reads `audience` in both cases — it does not identify\nwhich field was actually at fault.\n"
          },
          "401": {
            "$ref": "#/components/responses/DspUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/DspForbidden"
          },
          "429": {
            "$ref": "#/components/responses/DspInventoryRateLimited"
          }
        },
        "security": [
          {
            "dspBearerAuth": []
          },
          {
            "dspApiKeyAuth": []
          }
        ],
        "summary": "Rank creatives for an audience context",
        "tags": [
          "Audience Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/audience/semantic-search": {
      "get": {
        "description": "Free-text search over embedded scene descriptions, returning screens\nwhose recent observed context best matches the query.\n\n`min_similarity` filters on cosine distance, so raising it shrinks the\nresult set rather than reordering it. `since` bounds how far back the\nobservations may be drawn from.\n\n## Authentication\n\nThis is a DSP surface, not a portal one. It takes the same\n**platform API key** as `GET /openrtb/v2/adslots` — as\n`Authorization: Bearer <platform_api_key>` or an `x-api-key` header —\nand the key must hold the `inventory:read` scope, which every onboarded\nDSP key carries. A portal user's JWT is not accepted here. The route\nshares the DSP inventory rate-limit budget.\n",
        "operationId": "semanticAudienceSearch",
        "parameters": [
          {
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 20,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "since",
            "schema": {
              "default": "24h",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "min_similarity",
            "schema": {
              "default": 0.5,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "query": "commuters waiting on a rainy platform",
                  "results": [
                    {
                      "contextual_relevance": 0.71,
                      "created_at": "2026-08-30T21:04:11.000Z",
                      "mongo_screen_id": "507f1f77bcf86cd799439055",
                      "scene_description": "Covered transit platform at dusk, light rain, a dozen standing commuters facing the track.",
                      "screen_id": "507f1f77bcf86cd799439055",
                      "similarity": 0.8413925170898438
                    }
                  ],
                  "results_count": 1,
                  "time_window": "24h"
                },
                "schema": {
                  "properties": {
                    "query": {
                      "description": "Your search string, trimmed.",
                      "type": "string"
                    },
                    "results": {
                      "items": {
                        "properties": {
                          "contextual_relevance": {
                            "format": "double",
                            "type": "number"
                          },
                          "created_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "mongo_screen_id": {
                            "type": "string"
                          },
                          "scene_description": {
                            "description": "The embedded text the match was made against.",
                            "type": "string"
                          },
                          "screen_id": {
                            "type": "string"
                          },
                          "similarity": {
                            "description": "Cosine similarity in `0..1`, unrounded.",
                            "format": "double",
                            "type": "number"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "results_count": {
                      "type": "integer"
                    },
                    "time_window": {
                      "description": "The `since` value you sent, echoed verbatim.",
                      "example": "24h",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Screens whose recent observed scenes best match the query.\n\n`time_window` echoes the `since` value you sent rather than\nresolving it to timestamps, and `query` echoes your trimmed search\nstring. As with the other audience operations, an empty\n`results` list does not distinguish \"no match\" from \"backend\nunavailable\".\n\nNote `similarity` here is **not rounded**, unlike the lookalike\noperation's — expect full float precision.\n"
          }
        },
        "security": [
          {
            "dspBearerAuth": []
          },
          {
            "dspApiKeyAuth": []
          }
        ],
        "summary": "Semantic search across audience scene descriptions",
        "tags": [
          "Audience Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/audience/semantic-stats": {
      "get": {
        "description": "Coverage of the scene-embedding corpus that backs\n`GET /openrtb/v2/audience/semantic-search`, over the **last 24 hours\nonly**. Use it to decide whether a semantic search is worth issuing\nbefore you issue one.\n\nTakes no parameters and no window can be requested.\n\n## Read the counters as strings\n\n`total_embeddings` and `unique_screens` are 64-bit database counters and\nare serialised as **JSON strings** (`\"14523\"`, not `14523`). Coerce\nbefore comparing or summing, and do not type them as integers in a\ngenerated client or deserialisation will fail.\n\nOn an empty 24-hour window the two counts are the string `\"0\"` while\n`earliest` and `latest` are `null`.\n\n## `stats` can be null\n\nWhen the backing store is unavailable the endpoint still returns `200`,\nwith `stats` set to `null` rather than an error. A `null` here means\n\"coverage unknown\", not \"no embeddings\" — do not report it as zero\ncoverage.\n",
        "operationId": "getAudienceSemanticStats",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "stats": {
                    "earliest": "2026-08-31T09:14:02.117Z",
                    "latest": "2026-09-01T08:59:41.882Z",
                    "total_embeddings": "14523",
                    "unique_screens": "312"
                  }
                },
                "schema": {
                  "properties": {
                    "stats": {
                      "description": "`null` when the backing store was unavailable.",
                      "nullable": true,
                      "properties": {
                        "earliest": {
                          "description": "Oldest embedding in the window; `null` when empty.",
                          "format": "date-time",
                          "nullable": true,
                          "type": "string"
                        },
                        "latest": {
                          "description": "Newest embedding in the window; `null` when empty.",
                          "format": "date-time",
                          "nullable": true,
                          "type": "string"
                        },
                        "total_embeddings": {
                          "description": "Scene embeddings written in the last 24 hours.\nString-encoded 64-bit integer.\n",
                          "type": "string"
                        },
                        "unique_screens": {
                          "description": "Distinct screens that contributed. String-encoded\n64-bit integer.\n",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Embedding coverage for the last 24 hours, or `null` when the backing\nstore could not be reached.\n"
          },
          "401": {
            "$ref": "#/components/responses/DspUnauthorized"
          },
          "403": {
            "$ref": "#/components/responses/DspForbidden"
          },
          "429": {
            "$ref": "#/components/responses/DspInventoryRateLimited"
          }
        },
        "security": [
          {
            "dspBearerAuth": []
          },
          {
            "dspApiKeyAuth": []
          }
        ],
        "summary": "Get embedding statistics for semantic search",
        "tags": [
          "Audience Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/bid": {
      "post": {
        "description": "Submit a standard OpenRTB 2.6 bid response. Bids are validated against\ncreative blocklists and floor prices, then entered into the live waterfall\nauction (or scored-only in sandbox mode).\n\n**Each bid requires:**\n- `impid` — Impression ID from the `/adslots` bid request\n- `price` — CPM bid price in USD (must be > 0)\n- `adm` OR `nurl` — Either inline ad markup (VAST XML, HTML) or a win notice URL\n\nBids missing both `adm` and `nurl` are rejected with reason `missing_adm_and_nurl`.\nBids below the floor price are rejected with reason `below_floor` (the floor price\nand your bid price are included in the rejection for transparency).\n\n**Response includes per-bid granularity:** each bid is individually accepted or\nrejected, so a single bid response can have both accepted and rejected bids.\n\n**`nurl` macro contract (PR 4):** when your cached bid wins delivery, Trillboards\nfires a GET to your `nurl` URL with these macros expanded before the request\nleaves our edge:\n\n| Macro | Replaced with |\n| --- | --- |\n| `${AUCTION_ID}` | request id of the served opportunity |\n| `${AUCTION_BID_ID}` | the `bid.id` from your seatbid |\n| `${AUCTION_IMP_ID}` | the `bid.impid` |\n| `${AUCTION_SEAT_ID}` | your DSP seat |\n| `${AUCTION_AD_ID}` | the `bid.adid` |\n| `${AUCTION_PRICE}` | the clearing CPM (USD) |\n| `${AUCTION_CURRENCY}` | always `USD` today |\n\nThe nurl call is fire-and-forget on the server side — a 4xx from your\nendpoint never blocks the served VAST, but it WILL surface in our\npartner-recon reports as a delivery anomaly.\n\n**Rate limit:** 1,000 requests/minute (production), 100 requests/minute (sandbox).\n",
        "operationId": "acceptBidResponse",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "cur": "USD",
                "id": "br_8f3a2c1e-9d47-4b6a-a1c3-2f8e5d7b9a04",
                "seatbid": [
                  {
                    "bid": [
                      {
                        "adm": "<VAST version=\"4.0\"><Ad><InLine><AdSystem>Acme DSP</AdSystem><AdTitle>Summer Sale</AdTitle><Creatives><Creative><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\"progressive\" type=\"video/mp4\" width=\"1920\" height=\"1080\">https://cdn.acme-ads.com/creatives/summer-sale-15s.mp4</MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
                        "adomain": [
                          "acme-retail.com"
                        ],
                        "crid": "crid_summer_sale_15s",
                        "id": "bid_acme_20260403_001",
                        "impid": "imp_001",
                        "price": 8.5
                      },
                      {
                        "adomain": [
                          "acme-retail.com"
                        ],
                        "crid": "crid_spring_collection_30s",
                        "id": "bid_acme_20260403_002",
                        "impid": "imp_002",
                        "nurl": "https://acme-ads.com/vast/win?bid=${AUCTION_ID}&price=${AUCTION_PRICE}",
                        "price": 6.25
                      }
                    ],
                    "seat": "acme_ads"
                  }
                ]
              },
              "schema": {
                "description": "Standard OpenRTB 2.6 bid response object. Must contain at least one\nseatbid with at least one bid. Each bid must reference a valid impression\nID from a recent `/adslots` response.\n",
                "properties": {
                  "cur": {
                    "default": "USD",
                    "description": "Bid currency (currently only USD is supported)",
                    "example": "USD",
                    "type": "string"
                  },
                  "id": {
                    "description": "Bid response ID. Must match the bid request ID from `/adslots`.\nUsed for correlation in win/loss notifications.\n",
                    "example": "br_8f3a2c1e-9d47-4b6a-a1c3-2f8e5d7b9a04",
                    "type": "string"
                  },
                  "seatbid": {
                    "description": "Array of seat bid objects, one per buyer seat",
                    "items": {
                      "properties": {
                        "bid": {
                          "description": "Array of individual bid objects",
                          "items": {
                            "properties": {
                              "adm": {
                                "description": "Inline ad markup. For video ads, this should be VAST XML.\nFor display ads, HTML markup. Either `adm` or `nurl` must\nbe provided.\n",
                                "example": "<VAST version=\"4.0\"><Ad><InLine><AdSystem>Acme DSP</AdSystem><AdTitle>Summer Sale</AdTitle><Creatives><Creative><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\"progressive\" type=\"video/mp4\" width=\"1920\" height=\"1080\">https://cdn.acme-ads.com/creatives/summer-sale-15s.mp4</MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>",
                                "type": "string"
                              },
                              "adomain": {
                                "description": "Advertiser domains for blocklist checking. Include all\ndomains that appear in the creative landing page.\n",
                                "example": [
                                  "acme-retail.com",
                                  "acme-ads.com"
                                ],
                                "items": {
                                  "type": "string"
                                },
                                "type": "array"
                              },
                              "crid": {
                                "description": "Creative ID from your DSP. Used for creative-level\nreporting and blocklist management.\n",
                                "example": "crid_summer_sale_15s",
                                "type": "string"
                              },
                              "dealid": {
                                "description": "Deal ID if this bid is for a PMP or preferred deal.\nMust reference an active deal for your seat.\n",
                                "example": "deal_retail_q2_2026",
                                "type": "string"
                              },
                              "h": {
                                "description": "Creative height in pixels",
                                "example": 1080,
                                "type": "integer"
                              },
                              "id": {
                                "description": "Unique bid ID generated by your DSP. Used in win/loss\nnotifications and reporting. If omitted, Trillboards\ngenerates one.\n",
                                "example": "bid_acme_20260403_001",
                                "type": "string"
                              },
                              "impid": {
                                "description": "Impression ID from the bid request. Must match an `imp.id`\nfrom the `/adslots` response.\n",
                                "example": "imp_001",
                                "type": "string"
                              },
                              "nurl": {
                                "description": "Win notice URL. Called when this bid wins the auction.\nThe response body should contain the ad markup. Either\n`adm` or `nurl` must be provided.\n\nA URL template: it may carry the macros in the table\nabove (`${AUCTION_PRICE}`, `${AUCTION_ID}` and the rest),\nwhich Trillboards expands before the call. `{` and `}`\nare not legal in a URI, so the field is a string with no\n`uri` format.\n",
                                "example": "https://acme-ads.com/vast/win?bid=${AUCTION_ID}&price=${AUCTION_PRICE}",
                                "type": "string"
                              },
                              "price": {
                                "description": "Bid price in CPM (USD). Must be greater than 0. Bids below\nthe floor price are rejected with reason `below_floor`.\n",
                                "example": 8.5,
                                "minimum": 0.01,
                                "type": "number"
                              },
                              "w": {
                                "description": "Creative width in pixels",
                                "example": 1920,
                                "type": "integer"
                              }
                            },
                            "required": [
                              "impid",
                              "price"
                            ],
                            "type": "object"
                          },
                          "minItems": 1,
                          "type": "array"
                        },
                        "seat": {
                          "description": "Buyer seat ID (must match your registered seat_id)",
                          "example": "acme_ads",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "minItems": 1,
                    "type": "array"
                  }
                },
                "required": [
                  "id",
                  "seatbid"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "id": "br_8f3a2c1e-9d47-4b6a-a1c3-2f8e5d7b9a04",
                  "nbr": 0,
                  "rejections": null
                },
                "schema": {
                  "properties": {
                    "id": {
                      "description": "Bid request ID echoed back",
                      "example": "br_8f3a2c1e-9d47-4b6a-a1c3-2f8e5d7b9a04",
                      "type": "string"
                    },
                    "nbr": {
                      "description": "OpenRTB no-bid reason code (0=accepted, see OpenRTB 2.6 Table 5.24). Common values: 0=accepted, 1=unknown error, 2=known web spider, 3=suspicious activity, 4=URL blocklist, 5=below floor, 6=unresolvable creative, 7=duplicate, 8=no matching impressions.\n",
                      "example": 0,
                      "type": "integer"
                    },
                    "rejections": {
                      "description": "Per-impression rejection details. Only present when one or more\nbids are rejected. Each entry identifies the impression and reason.\n",
                      "items": {
                        "properties": {
                          "impid": {
                            "description": "Impression ID that was rejected",
                            "example": "imp_001",
                            "type": "string"
                          },
                          "nbr": {
                            "description": "OpenRTB no-bid reason code for this impression",
                            "example": 5,
                            "type": "integer"
                          },
                          "reason": {
                            "description": "Human-readable rejection reason",
                            "example": "below_floor",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "nullable": true,
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Bid processing result. Each bid is individually accepted or rejected.\nA single response can contain both accepted and rejected bids.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 1000,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 997,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 42,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid bid response: seatbid array is required and must not be empty",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid bid response. The request body must be a valid OpenRTB 2.6 bid response\nwith at least one seatbid containing at least one bid.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "415": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Content-Type must be application/json",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Content-Type must be `application/json`. Ensure your request includes\nthe header `Content-Type: application/json`.\n"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Submit OpenRTB 2.6 bid response",
        "tags": [
          "Bidding"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/bid\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"id\":\"br_8f3a2c1e-9d47-4b6a-a1c3-2f8e5d7b9a04\",\"seatbid\":[{\"bid\":[{\"id\":\"bid_acme_20260403_001\",\"impid\":\"imp_001\",\"price\":8.5,\"adm\":\"<VAST version=\\\"4.0\\\"><Ad><InLine><AdSystem>Acme DSP</AdSystem><AdTitle>Summer Sale</AdTitle><Creatives><Creative><Linear><Duration>00:00:15</Duration><MediaFiles><MediaFile delivery=\\\"progressive\\\" type=\\\"video/mp4\\\" width=\\\"1920\\\" height=\\\"1080\\\">https://cdn.acme-ads.com/creatives/summer-sale-15s.mp4</MediaFile></MediaFiles></Linear></Creative></Creatives></InLine></Ad></VAST>\",\"adomain\":[\"acme-retail.com\"],\"crid\":\"crid_summer_sale_15s\"},{\"id\":\"bid_acme_20260403_002\",\"impid\":\"imp_002\",\"price\":6.25,\"nurl\":\"https://acme-ads.com/vast/win?bid=${AUCTION_ID}&price=${AUCTION_PRICE}\",\"adomain\":[\"acme-retail.com\"],\"crid\":\"crid_spring_collection_30s\"}],\"seat\":\"acme_ads\"}],\"cur\":\"USD\"}'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "paid",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/campaigns": {
      "post": {
        "description": "Creates a campaign linking an approved creative to targeting criteria. The API\nreturns matching screens based on your targeting, so you can choose which\nspecific screens to assign.\n\n**The creative must have `status: approved`** (passed AI moderation). Campaigns\ncannot be created with pending or rejected creatives.\n\n**Workflow:**\n1. Create campaign with targeting criteria (this endpoint)\n2. Review the `screens` array in the response\n3. Call `POST /campaigns/assign` with your selected `screen_ids`\n\nThe response includes up to 20 matching screens. If `matching_screens` exceeds\n20, use the `/adslots` endpoint with the same targeting filters to browse\nthe full inventory.\n\n**Rate limit:** 100 requests/minute.\n",
        "operationId": "createCampaign",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "budget_usd": 2500,
                "creative_id": "6620abc123def456789abcde",
                "name": "Summer Retail Push - NYC",
                "targeting": {
                  "audience_segment_ids": [
                    "iab:653"
                  ],
                  "blocked_iab_categories": [
                    "IAB7-39"
                  ],
                  "budget_pacing": "even",
                  "country": "US",
                  "daypart": [
                    {
                      "dow": 1,
                      "hour_end": 17,
                      "hour_start": 9
                    }
                  ],
                  "frequency_cap_per_device_per_day": 5,
                  "geo_lat": 40.7128,
                  "geo_lng": -74.006,
                  "geo_radius_km": 10,
                  "state": "NY",
                  "venue_type": "retail"
                }
              },
              "schema": {
                "properties": {
                  "budget_usd": {
                    "description": "Optional campaign budget in USD. Used for pacing and budget\nalerts but does not hard-stop delivery. Set to null for\nuncapped campaigns.\n",
                    "example": 2500,
                    "minimum": 0.01,
                    "nullable": true,
                    "type": "number"
                  },
                  "creative_id": {
                    "description": "ID of an approved creative (from `POST /creatives`). The creative\nmust have `status: approved` or `status: active`. Pending or\nrejected creatives will return 422.\n",
                    "example": "6620abc123def456789abcde",
                    "type": "string"
                  },
                  "name": {
                    "description": "Campaign name for your reference. Appears in reporting dashboards.\n",
                    "example": "Summer Retail Push - NYC",
                    "maxLength": 200,
                    "minLength": 1,
                    "type": "string"
                  },
                  "targeting": {
                    "description": "Targeting criteria for matching screens. All criteria are combined\nwith AND logic. Omitted fields match all values.\n\nCompound targeting fields (`daypart`, `geo_lat`/`geo_lng`/`geo_radius_km`,\n`audience_segment_ids`, `blocked_iab_categories`, `blocked_advertiser_domains`,\n`frequency_cap_per_device_per_day`, `budget_pacing`) are enforced server-side\nat waterfall-mediation time — bids on screens that fail any filter are\ndropped before reaching delivery.\n",
                    "properties": {
                      "audience_segment_ids": {
                        "description": "Required audience segment IDs. Each ID must resolve in\nthe `@trillboards/iab-taxonomy` package — either an IAB\nAudience Taxonomy 1.1 leaf (segtax=4) or a Trillboards\nsegment (segtax=600). Bids on screens whose live\naudience signals do NOT intersect this list are dropped.\n",
                        "items": {
                          "example": "iab:653",
                          "type": "string"
                        },
                        "nullable": true,
                        "type": "array"
                      },
                      "blocked_advertiser_domains": {
                        "description": "Lowercase advertiser hostnames blocked for competitive\nseparation. Used by the brand-safety layer.\n",
                        "items": {
                          "example": "competitor.com",
                          "type": "string"
                        },
                        "nullable": true,
                        "type": "array"
                      },
                      "blocked_iab_categories": {
                        "description": "IAB content categories the campaign WILL NOT serve next\nto. Compared against the incoming bid's OpenRTB `bid.cat`;\nany intersection blocks the bid.\n",
                        "items": {
                          "example": "IAB7-39",
                          "type": "string"
                        },
                        "nullable": true,
                        "type": "array"
                      },
                      "budget_pacing": {
                        "description": "Budget pacing model. `asap` = spend immediately; `even`\n= uniform across schedule; `front_loaded` = more early\nthen taper. Default null = asap.\n",
                        "enum": [
                          "asap",
                          "even",
                          "front_loaded"
                        ],
                        "nullable": true,
                        "type": "string"
                      },
                      "city": {
                        "description": "City name",
                        "example": "New York",
                        "type": "string"
                      },
                      "country": {
                        "description": "ISO 3166-1 alpha-2 country code",
                        "example": "US",
                        "type": "string"
                      },
                      "daypart": {
                        "description": "Array of daypart windows. Each row narrows delivery to a single\nday-of-week + hour range (inclusive). Multiple rows are OR'd.\nEmpty array or null means always-on.\n",
                        "items": {
                          "properties": {
                            "dow": {
                              "description": "Day of week (0=Sunday..6=Saturday)",
                              "maximum": 6,
                              "minimum": 0,
                              "type": "integer"
                            },
                            "hour_end": {
                              "description": "Inclusive end hour; must be >= hour_start",
                              "maximum": 23,
                              "minimum": 0,
                              "type": "integer"
                            },
                            "hour_start": {
                              "maximum": 23,
                              "minimum": 0,
                              "type": "integer"
                            }
                          },
                          "required": [
                            "dow",
                            "hour_start",
                            "hour_end"
                          ],
                          "type": "object"
                        },
                        "nullable": true,
                        "type": "array"
                      },
                      "frequency_cap_per_device_per_day": {
                        "description": "Maximum impressions per device per UTC day. NULL means\nuncapped.\n",
                        "minimum": 1,
                        "nullable": true,
                        "type": "integer"
                      },
                      "geo_lat": {
                        "description": "Latitude of geo-radius targeting center. Required if\n`geo_lng` or `geo_radius_km` are set.\n",
                        "maximum": 90,
                        "minimum": -90,
                        "nullable": true,
                        "type": "number"
                      },
                      "geo_lng": {
                        "description": "Longitude of geo-radius targeting center. Required if\n`geo_lat` or `geo_radius_km` are set.\n",
                        "maximum": 180,
                        "minimum": -180,
                        "nullable": true,
                        "type": "number"
                      },
                      "geo_radius_km": {
                        "description": "Radius in kilometers from (`geo_lat`, `geo_lng`). Required\nif `geo_lat` or `geo_lng` are set. Screens missing\nlat/lng metadata are excluded when geo radius is configured.\n",
                        "minimum": 0,
                        "not": {
                          "enum": [
                            0
                          ]
                        },
                        "nullable": true,
                        "type": "number"
                      },
                      "state": {
                        "description": "State or province code",
                        "example": "NY",
                        "type": "string"
                      },
                      "venue_type": {
                        "description": "Target venue type",
                        "example": "retail",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "creative_id",
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "campaign": {
                      "budget_usd": 2500,
                      "creative_id": "6620abc123def456789abcde",
                      "name": "Summer Retail Push - NYC",
                      "status": "active",
                      "targeting": {
                        "city": null,
                        "country": "US",
                        "state": "NY",
                        "venue_type": "retail"
                      }
                    },
                    "matching_screens": 87,
                    "next_step": "Call POST /openrtb/v2/campaigns/assign with creative_id and screen_ids to place your creative on specific screens.",
                    "screens": [
                      {
                        "city": "New York",
                        "country": "US",
                        "name": "Macy's Herald Square - Entrance Display",
                        "screen_id": "scr_7f8a9b0c1d2e3f4a",
                        "venue_type": "retail"
                      },
                      {
                        "city": "New York",
                        "country": "US",
                        "name": "Nordstrom Flagship - Shoe Dept",
                        "screen_id": "scr_2a3b4c5d6e7f8a9b",
                        "venue_type": "retail"
                      },
                      {
                        "city": "New York",
                        "country": "US",
                        "name": "Whole Foods Columbus Circle - Checkout",
                        "screen_id": "scr_1d2e3f4a5b6c7d8e",
                        "venue_type": "retail"
                      }
                    ]
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "campaign": {
                          "description": "Campaign details",
                          "properties": {
                            "budget_usd": {
                              "description": "Campaign budget (null if uncapped)",
                              "example": 2500,
                              "nullable": true,
                              "type": "number"
                            },
                            "creative_id": {
                              "description": "Creative assigned to this campaign",
                              "example": "6620abc123def456789abcde",
                              "type": "string"
                            },
                            "name": {
                              "description": "Campaign name",
                              "example": "Summer Retail Push - NYC",
                              "type": "string"
                            },
                            "status": {
                              "description": "Campaign status",
                              "example": "active",
                              "type": "string"
                            },
                            "targeting": {
                              "description": "Targeting criteria applied",
                              "properties": {
                                "city": {
                                  "example": null,
                                  "nullable": true,
                                  "type": "string"
                                },
                                "country": {
                                  "example": "US",
                                  "type": "string"
                                },
                                "state": {
                                  "example": "NY",
                                  "type": "string"
                                },
                                "venue_type": {
                                  "example": "retail",
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            }
                          },
                          "type": "object"
                        },
                        "matching_screens": {
                          "description": "Total number of screens matching the targeting criteria.\nMay exceed the number of screens in the `screens` array\n(which is capped at 20).\n",
                          "example": 87,
                          "type": "integer"
                        },
                        "next_step": {
                          "description": "Guidance on the next step — calling the assign endpoint\nwith selected screen IDs.\n",
                          "example": "Call POST /openrtb/v2/campaigns/assign with creative_id and screen_ids to place your creative on specific screens.",
                          "type": "string"
                        },
                        "screens": {
                          "description": "Up to 20 matching screens. Use these screen_id values with\n`POST /campaigns/assign`. For the full list, query `/adslots`\nwith the same targeting filters.\n",
                          "items": {
                            "properties": {
                              "city": {
                                "description": "City name",
                                "example": "New York",
                                "type": "string"
                              },
                              "country": {
                                "description": "Country code",
                                "example": "US",
                                "type": "string"
                              },
                              "name": {
                                "description": "Human-readable screen name",
                                "example": "Macy's Herald Square - Entrance Display",
                                "type": "string"
                              },
                              "screen_id": {
                                "description": "Screen identifier for assignment",
                                "example": "scr_7f8a9b0c1d2e3f4a",
                                "type": "string"
                              },
                              "venue_type": {
                                "description": "Venue classification",
                                "example": "retail",
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Campaign created with matching screen list",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "creative_id and name are required",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request parameters"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Creative 6620abc123def456789abcde not found",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Creative not found"
          },
          "422": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Creative 6620abc123def456789abcde has status 'pending_moderation'. Only approved creatives can be used in campaigns.",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Creative is not approved. Only creatives that have passed AI moderation\ncan be used in campaigns. Check `GET /creatives/{id}` for moderation status.\n"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Create a campaign with targeting",
        "tags": [
          "Campaigns"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/campaigns\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"creative_id\":\"6620abc123def456789abcde\",\"name\":\"Summer Retail Push - NYC\",\"budget_usd\":2500,\"targeting\":{\"country\":\"US\",\"state\":\"NY\",\"venue_type\":\"retail\",\"daypart\":[{\"dow\":1,\"hour_start\":9,\"hour_end\":17}],\"geo_lat\":40.7128,\"geo_lng\":-74.006,\"geo_radius_km\":10,\"audience_segment_ids\":[\"iab:653\"],\"blocked_iab_categories\":[\"IAB7-39\"],\"frequency_cap_per_device_per_day\":5,\"budget_pacing\":\"even\"}}'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "paid",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/campaigns/assign": {
      "post": {
        "description": "Assigns an approved creative to specific screens. Creates screen allocations\nthat make the ad visible in each screen's content playlist.\n\n**Screens pick up new assignments within 30-60 seconds** via their content\nrefresh cycle. No manual intervention is needed on the screen side.\n\n**Scheduling:** Optionally provide date and time ranges to control when the\ncreative is shown. If omitted, the creative runs indefinitely during all hours.\n\n**Limits:** Maximum 100 screen IDs per call. For larger deployments, make\nmultiple calls. Screen IDs come from the `/adslots` or `/campaigns` response.\n\n**Rate limit:** 100 requests/minute.\n",
        "operationId": "assignToScreens",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "creative_id": "6620abc123def456789abcde",
                "end_date": "2026-04-17",
                "end_time": "22:00",
                "screen_ids": [
                  "scr_7f8a9b0c1d2e3f4a",
                  "scr_2a3b4c5d6e7f8a9b",
                  "scr_1d2e3f4a5b6c7d8e"
                ],
                "start_date": "2026-04-10",
                "start_time": "08:00"
              },
              "schema": {
                "properties": {
                  "creative_id": {
                    "description": "ID of an approved creative to assign",
                    "example": "6620abc123def456789abcde",
                    "type": "string"
                  },
                  "end_date": {
                    "description": "Campaign end date (YYYY-MM-DD). If omitted, runs indefinitely\nuntil manually stopped.\n",
                    "example": "2026-04-17",
                    "format": "date",
                    "type": "string"
                  },
                  "end_time": {
                    "default": "23:59",
                    "description": "Daily end time in 24-hour format (HH:MM). The creative will only\nbe shown before this time each day. Default is \"23:59\".\n",
                    "example": "22:00",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "type": "string"
                  },
                  "screen_ids": {
                    "description": "Array of screen IDs to assign the creative to. Minimum 1,\nmaximum 100 per request. Get screen IDs from `/adslots`,\n`/campaigns`, or `/discover`.\n",
                    "example": [
                      "scr_7f8a9b0c1d2e3f4a",
                      "scr_2a3b4c5d6e7f8a9b",
                      "scr_1d2e3f4a5b6c7d8e"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 100,
                    "minItems": 1,
                    "type": "array"
                  },
                  "start_date": {
                    "description": "Campaign start date (YYYY-MM-DD). If omitted, starts immediately.\n",
                    "example": "2026-04-10",
                    "format": "date",
                    "type": "string"
                  },
                  "start_time": {
                    "default": "00:00",
                    "description": "Daily start time in 24-hour format (HH:MM). The creative will only\nbe shown after this time each day. Default is \"00:00\" (midnight).\n",
                    "example": "08:00",
                    "pattern": "^\\d{2}:\\d{2}$",
                    "type": "string"
                  }
                },
                "required": [
                  "creative_id",
                  "screen_ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "assigned": 3,
                    "assignments": [
                      {
                        "allocation_id": "alloc_9a8b7c6d5e4f3a2b",
                        "screen_id": "scr_7f8a9b0c1d2e3f4a",
                        "status": "active"
                      },
                      {
                        "allocation_id": "alloc_1b2c3d4e5f6a7b8c",
                        "screen_id": "scr_2a3b4c5d6e7f8a9b",
                        "status": "active"
                      },
                      {
                        "allocation_id": "alloc_4d5e6f7a8b9c0d1e",
                        "screen_id": "scr_1d2e3f4a5b6c7d8e",
                        "status": "active"
                      }
                    ],
                    "creative_id": "6620abc123def456789abcde",
                    "failed": 0,
                    "note": "Screens will begin showing this creative within their next content refresh cycle (~30-60 seconds).",
                    "schedule": {
                      "end_date": "2026-04-17",
                      "end_time": "22:00",
                      "start_date": "2026-04-10",
                      "start_time": "08:00"
                    }
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "assigned": {
                          "description": "Number of screens successfully assigned",
                          "example": 3,
                          "type": "integer"
                        },
                        "assignments": {
                          "description": "Individual assignment results per screen",
                          "items": {
                            "properties": {
                              "allocation_id": {
                                "description": "Unique allocation ID. Use this to reference or cancel\nspecific screen assignments.\n",
                                "example": "alloc_9a8b7c6d5e4f3a2b",
                                "type": "string"
                              },
                              "screen_id": {
                                "description": "Screen that was assigned",
                                "example": "scr_7f8a9b0c1d2e3f4a",
                                "type": "string"
                              },
                              "status": {
                                "description": "Assignment status",
                                "example": "active",
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "creative_id": {
                          "description": "Creative that was assigned",
                          "example": "6620abc123def456789abcde",
                          "type": "string"
                        },
                        "failed": {
                          "description": "Number of screens that could not be assigned (e.g., screen\nnot found, screen offline, screen already at max allocation).\n",
                          "example": 0,
                          "type": "integer"
                        },
                        "note": {
                          "description": "Informational message about content delivery timing",
                          "example": "Screens will begin showing this creative within their next content refresh cycle (~30-60 seconds).",
                          "type": "string"
                        },
                        "schedule": {
                          "description": "The schedule applied to all assignments",
                          "properties": {
                            "end_date": {
                              "description": "Campaign end date",
                              "example": "2026-04-17",
                              "type": "string"
                            },
                            "end_time": {
                              "description": "Daily end time",
                              "example": "22:00",
                              "type": "string"
                            },
                            "start_date": {
                              "description": "Campaign start date",
                              "example": "2026-04-10",
                              "type": "string"
                            },
                            "start_time": {
                              "description": "Daily start time",
                              "example": "08:00",
                              "type": "string"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Screens assigned successfully. Allocations are created immediately and\nscreens will begin showing the creative within 30-60 seconds.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "creative_id and screen_ids are required. screen_ids must be an array of 1-100 screen IDs.",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request parameters"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Creative 6620abc123def456789abcde is not approved for assignment",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Creative not approved or not found"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Place creative on specific screens",
        "tags": [
          "Campaigns"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/campaigns/assign\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"creative_id\":\"6620abc123def456789abcde\",\"screen_ids\":[\"scr_7f8a9b0c1d2e3f4a\",\"scr_2a3b4c5d6e7f8a9b\",\"scr_1d2e3f4a5b6c7d8e\"],\"start_date\":\"2026-04-10\",\"end_date\":\"2026-04-17\",\"start_time\":\"08:00\",\"end_time\":\"22:00\"}'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "paid",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/creatives": {
      "get": {
        "description": "Returns all creatives for the authenticated DSP with moderation status, IAB\ncategories, and pagination. Use the `status` filter to find creatives in a\nspecific state (e.g., only approved creatives ready for campaigns).\n",
        "operationId": "listCreatives",
        "parameters": [
          {
            "description": "Filter by creative status. If omitted, returns creatives in all statuses.\n",
            "example": "approved",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "pending_moderation",
                "approved",
                "rejected",
                "active",
                "deactivated"
              ],
              "type": "string"
            }
          },
          {
            "description": "Maximum number of creatives to return per page. Values above 200 are capped to 200 silently (the server will not return an error).\n",
            "example": 50,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Number of creatives to skip for pagination",
            "example": 0,
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "asset_url": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/summer-sale-15s.mp4",
                      "content_type": "video/mp4",
                      "created_at": "2026-04-02T10:15:30.000Z",
                      "creative_id": "6620abc123def456789abcde",
                      "iab_categories": [
                        "IAB18",
                        "IAB22-1"
                      ],
                      "moderation_status": "approved",
                      "name": "Summer Sale 15s Video",
                      "status": "approved"
                    },
                    {
                      "asset_url": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/spring-collection.jpg",
                      "content_type": "image/jpeg",
                      "created_at": "2026-03-28T16:42:00.000Z",
                      "creative_id": "6620def456789abcde012345",
                      "iab_categories": [
                        "IAB18-1",
                        "IAB22"
                      ],
                      "moderation_status": "approved",
                      "name": "Spring Collection Banner",
                      "status": "active"
                    },
                    {
                      "asset_url": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/holiday-promo.mp4",
                      "content_type": "video/mp4",
                      "created_at": "2026-04-03T14:30:00.000Z",
                      "creative_id": "6620fed987654321cba09876",
                      "iab_categories": [],
                      "moderation_status": "pending",
                      "name": "Holiday Promo 30s",
                      "status": "pending_moderation"
                    }
                  ],
                  "pagination": {
                    "count": 12,
                    "limit": 50,
                    "offset": 0
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "description": "Array of creative summary objects",
                      "items": {
                        "properties": {
                          "asset_url": {
                            "description": "CDN URL of the creative asset",
                            "example": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/summer-sale-15s.mp4",
                            "type": "string"
                          },
                          "content_type": {
                            "description": "MIME type",
                            "example": "video/mp4",
                            "type": "string"
                          },
                          "created_at": {
                            "description": "Creation timestamp",
                            "example": "2026-04-02T10:15:30.000Z",
                            "format": "date-time",
                            "type": "string"
                          },
                          "creative_id": {
                            "description": "Unique creative identifier",
                            "example": "6620abc123def456789abcde",
                            "type": "string"
                          },
                          "iab_categories": {
                            "description": "IAB Content Taxonomy 1.0 category codes assigned by AI moderation",
                            "example": [
                              "IAB18",
                              "IAB22-1"
                            ],
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          "moderation_status": {
                            "description": "AI moderation pipeline status",
                            "enum": [
                              "pending",
                              "approved",
                              "rejected"
                            ],
                            "example": "approved",
                            "type": "string"
                          },
                          "name": {
                            "description": "Creative name",
                            "example": "Summer Sale 15s Video",
                            "type": "string"
                          },
                          "status": {
                            "description": "Current creative status",
                            "enum": [
                              "pending_moderation",
                              "approved",
                              "rejected",
                              "active",
                              "deactivated"
                            ],
                            "example": "approved",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "description": "Pagination metadata",
                      "properties": {
                        "count": {
                          "description": "Total number of creatives matching the filter",
                          "example": 12,
                          "type": "integer"
                        },
                        "limit": {
                          "description": "Number of results per page",
                          "example": 50,
                          "type": "integer"
                        },
                        "offset": {
                          "description": "Number of results skipped",
                          "example": 0,
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "description": "Whether the request was successful",
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "List of creatives with moderation status",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List your creatives",
        "tags": [
          "Creatives"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/creatives\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      },
      "post": {
        "description": "Upload a creative asset for automatic Gemini AI moderation. Two upload modes\nare supported:\n\n**URL mode** — Provide a `creative_url` pointing to your hosted asset. The URL\nmust be publicly accessible. Trillboards downloads the asset and begins AI\nmoderation immediately.\n\n```json\n{\n  \"name\": \"Summer Sale 15s\",\n  \"content_type\": \"video/mp4\",\n  \"creative_url\": \"https://cdn.example.com/ads/summer-sale.mp4\"\n}\n```\n\n**Upload mode** — Get a presigned S3 URL for direct upload. Provide a `filename`\ninstead of `creative_url`. The response includes `upload.upload_url` — PUT your\nfile there with the specified Content-Type header, then call\n`POST /creatives/{id}/confirm` to trigger moderation.\n\n```json\n{\n  \"name\": \"Summer Sale 15s\",\n  \"content_type\": \"video/mp4\",\n  \"filename\": \"summer-sale.mp4\"\n}\n```\n\n**AI moderation** typically completes within 30 seconds and produces:\n- Content classification (8 safety flags)\n- MPAA-style age rating (G through NC-17)\n- Brand recognition\n- IAB Content Taxonomy 1.0 category codes\n- Content analysis (subjects, setting, mood, quality, keywords)\n\nPoll `GET /creatives/{id}` to check moderation status. Only creatives with\n`status: approved` can be used in campaigns.\n\n**Rate limit:** 100 requests/minute.\n",
        "operationId": "createCreative",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "campaign_link": "https://acme-retail.com/summer-sale",
                "content_type": "video/mp4",
                "creative_url": "https://cdn.acme-ads.com/creatives/summer-sale-15s.mp4",
                "description": "15-second video promoting summer retail sale with beach imagery",
                "name": "Summer Sale 15s Video"
              },
              "schema": {
                "description": "Creative upload request. Exactly one of `creative_url` (URL mode) or\n`filename` (upload mode) must be provided. If neither is present, the\nrequest is rejected with 400.\n",
                "properties": {
                  "campaign_link": {
                    "description": "Click-through URL for interactive screens. Opened when a viewer\ntaps or interacts with the ad on touch-enabled displays.\n",
                    "example": "https://acme-retail.com/summer-sale",
                    "format": "uri",
                    "type": "string"
                  },
                  "content_type": {
                    "description": "MIME type of the creative asset. Must match the actual file format.\nMismatched content types will cause moderation failures.\n",
                    "enum": [
                      "video/mp4",
                      "video/quicktime",
                      "video/webm",
                      "image/jpeg",
                      "image/png",
                      "image/webp",
                      "image/gif"
                    ],
                    "example": "video/mp4",
                    "type": "string"
                  },
                  "creative_url": {
                    "description": "**URL mode:** Publicly accessible URL of the creative asset.\nTrillboards downloads the asset and begins moderation immediately.\nMust be HTTPS. Maximum file size: 100 MB.\n",
                    "example": "https://cdn.acme-ads.com/creatives/summer-sale-15s.mp4",
                    "format": "uri",
                    "type": "string"
                  },
                  "description": {
                    "description": "Optional description of the creative content",
                    "example": "15-second video promoting summer retail sale with beach imagery",
                    "maxLength": 1000,
                    "type": "string"
                  },
                  "filename": {
                    "description": "**Upload mode:** Filename for presigned S3 upload. The response will\ninclude a `upload.upload_url` for direct file upload. After uploading,\ncall `POST /creatives/{id}/confirm` to trigger moderation.\n",
                    "example": "summer-sale-15s.mp4",
                    "type": "string"
                  },
                  "name": {
                    "description": "Human-readable creative name for your reference. Appears in\nreporting dashboards and moderation results.\n",
                    "example": "Summer Sale 15s Video",
                    "maxLength": 200,
                    "minLength": 1,
                    "type": "string"
                  }
                },
                "required": [
                  "name",
                  "content_type"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "asset_url": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/summer-sale-15s.mp4",
                    "content_type": "video/mp4",
                    "creative_id": "6620abc123def456789abcde",
                    "moderation": {
                      "description": "Creative submitted for AI moderation. Poll GET /creatives/6620abc123def456789abcde for results (~30 seconds).",
                      "status": "queued"
                    },
                    "name": "Summer Sale 15s Video",
                    "status": "pending_moderation",
                    "upload": null
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "description": "Created creative details",
                      "properties": {
                        "asset_url": {
                          "description": "CDN URL where the creative is (or will be) hosted. For URL mode,\nthis is the Trillboards CDN copy. For upload mode, this is\npopulated after upload and confirmation.\n",
                          "example": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/summer-sale-15s.mp4",
                          "type": "string"
                        },
                        "content_type": {
                          "description": "MIME type of the creative",
                          "example": "video/mp4",
                          "type": "string"
                        },
                        "creative_id": {
                          "description": "Unique creative identifier. Use this in campaigns and to poll moderation status.",
                          "example": "6620abc123def456789abcde",
                          "type": "string"
                        },
                        "moderation": {
                          "description": "Current moderation pipeline status",
                          "properties": {
                            "description": {
                              "description": "Human-readable moderation status message",
                              "example": "Creative submitted for AI moderation. Poll GET /creatives/6620abc123def456789abcde for results (~30 seconds).",
                              "type": "string"
                            },
                            "status": {
                              "description": "Moderation pipeline status:\n- `queued` — Waiting in the moderation queue\n- `processing` — AI moderation is running\n- `completed` — Moderation finished (check creative status for result)\n",
                              "enum": [
                                "queued",
                                "processing",
                                "completed"
                              ],
                              "example": "queued",
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "name": {
                          "description": "Creative name as provided",
                          "example": "Summer Sale 15s Video",
                          "type": "string"
                        },
                        "status": {
                          "description": "Current creative status:\n- `pending_moderation` — Waiting for AI moderation to complete\n- `approved` — Passed moderation, ready for campaigns\n- `rejected` — Failed moderation (check flags for details)\n- `active` — Currently assigned to one or more screens\n- `deactivated` — Manually deactivated by DSP\n",
                          "enum": [
                            "pending_moderation",
                            "approved",
                            "rejected",
                            "active",
                            "deactivated"
                          ],
                          "example": "pending_moderation",
                          "type": "string"
                        },
                        "upload": {
                          "description": "Upload instructions. **Only present in upload mode** (when `filename`\nwas provided instead of `creative_url`). PUT your file to `upload_url`\nwith the specified Content-Type header, then call\n`POST /creatives/{id}/confirm`.\n",
                          "nullable": true,
                          "properties": {
                            "cdn_url": {
                              "description": "CDN URL where the file will be served after upload",
                              "example": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/summer-sale-15s.mp4",
                              "type": "string"
                            },
                            "expires_in": {
                              "description": "Presigned URL expiration time in seconds",
                              "example": 3600,
                              "type": "integer"
                            },
                            "headers": {
                              "description": "Required headers for the upload request",
                              "properties": {
                                "Content-Type": {
                                  "description": "Content-Type header value for the upload",
                                  "example": "video/mp4",
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            },
                            "method": {
                              "description": "HTTP method to use for upload",
                              "example": "PUT",
                              "type": "string"
                            },
                            "upload_url": {
                              "description": "Presigned S3 URL for file upload. Send a PUT request with your\nfile as the body and the specified Content-Type header.\n",
                              "example": "https://trillboards-dsp-uploads.s3.amazonaws.com/acme_ads/summer-sale-15s.mp4?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...",
                              "type": "string"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "description": "Whether the creative was created successfully",
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Creative created and moderation enqueued. For URL mode, moderation begins\nimmediately. For upload mode, moderation begins after you upload the file\nand call `POST /creatives/{id}/confirm`.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Either creative_url or filename must be provided",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request. Common causes:\n- Missing required field `name` or `content_type`\n- Neither `creative_url` nor `filename` provided\n- Invalid `content_type` value\n- `creative_url` is not a valid HTTPS URL\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Upload a creative for AI moderation",
        "tags": [
          "Creatives"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/creatives\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Summer Sale 15s Video\",\"content_type\":\"video/mp4\",\"creative_url\":\"https://cdn.acme-ads.com/creatives/summer-sale-15s.mp4\",\"description\":\"15-second video promoting summer retail sale with beach imagery\",\"campaign_link\":\"https://acme-retail.com/summer-sale\"}'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/creatives/{id}": {
      "get": {
        "description": "Returns full creative details including AI moderation results. Use this endpoint\nto:\n\n- **Poll moderation status** after uploading a creative (check `moderation.status`)\n- **Review content classification** flags (alcohol, tobacco, gambling, etc.)\n- **Check age rating** (G, PG, PG-13, R, NC-17)\n- **View IAB categories** assigned by the AI pipeline\n- **See content analysis** (subjects, setting, mood, quality, keywords)\n- **Verify brand detection** results\n\n**Moderation timeline:**\n1. `status: pending_moderation` — Creative is in the queue (0-10 seconds)\n2. `status: pending_moderation` with `moderation.status: processing` — AI is analyzing (10-25 seconds)\n3. `status: approved` or `status: rejected` — Moderation complete (~30 seconds total)\n\n**Tip:** Poll every 5 seconds until `moderation.status` is `completed`.\n",
        "operationId": "getCreative",
        "parameters": [
          {
            "description": "Creative ID returned from `POST /creatives`",
            "example": "6620abc123def456789abcde",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "asset_url": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/summer-sale-15s.mp4",
                    "campaign_link": "https://acme-retail.com/summer-sale",
                    "content_type": "video/mp4",
                    "created_at": "2026-04-02T10:15:30.000Z",
                    "creative_id": "6620abc123def456789abcde",
                    "description": "15-second video promoting summer retail sale with beach imagery",
                    "moderation": {
                      "analysis": {
                        "keywords": [
                          "summer",
                          "sale",
                          "fashion",
                          "beach",
                          "discount"
                        ],
                        "mood": "upbeat, energetic",
                        "quality": "professional",
                        "setting": "beachfront retail store",
                        "subjects": [
                          "beach",
                          "summer clothing",
                          "retail store"
                        ]
                      },
                      "brand": "Acme Retail",
                      "category": "Retail/Fashion",
                      "content_classification": {
                        "age_rating": "G",
                        "confidence": 0.94,
                        "contains_adult_content": false,
                        "contains_alcohol": false,
                        "contains_firearms": false,
                        "contains_gambling": false,
                        "contains_political": false,
                        "contains_profanity": false,
                        "contains_tobacco": false,
                        "contains_violence": false,
                        "is_known_brand": true
                      },
                      "flags": [],
                      "reviewed_at": "2026-04-02T10:16:02.000Z",
                      "status": "approved"
                    },
                    "name": "Summer Sale 15s Video",
                    "openrtb": {
                      "cat": [
                        "IAB18",
                        "IAB22-1"
                      ]
                    },
                    "status": "approved",
                    "updated_at": "2026-04-02T10:16:02.000Z"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "asset_url": {
                          "description": "CDN URL of the creative",
                          "example": "https://d1a2b3c4d5e6.cloudfront.net/dsp-creatives/acme_ads/summer-sale-15s.mp4",
                          "type": "string"
                        },
                        "campaign_link": {
                          "description": "Click-through URL for interactive screens",
                          "example": "https://acme-retail.com/summer-sale",
                          "nullable": true,
                          "type": "string"
                        },
                        "content_type": {
                          "description": "MIME type of the creative asset",
                          "example": "video/mp4",
                          "type": "string"
                        },
                        "created_at": {
                          "description": "Creative creation timestamp",
                          "example": "2026-04-02T10:15:30.000Z",
                          "format": "date-time",
                          "type": "string"
                        },
                        "creative_id": {
                          "description": "Unique creative identifier",
                          "example": "6620abc123def456789abcde",
                          "type": "string"
                        },
                        "description": {
                          "description": "Creative description (if provided during upload)",
                          "example": "15-second video promoting summer retail sale with beach imagery",
                          "nullable": true,
                          "type": "string"
                        },
                        "moderation": {
                          "description": "AI moderation results from Gemini",
                          "properties": {
                            "analysis": {
                              "description": "AI content analysis results",
                              "nullable": true,
                              "properties": {
                                "keywords": {
                                  "description": "Extracted keywords for categorization",
                                  "example": [
                                    "summer",
                                    "sale",
                                    "fashion",
                                    "beach",
                                    "discount"
                                  ],
                                  "items": {
                                    "type": "string"
                                  },
                                  "type": "array"
                                },
                                "mood": {
                                  "description": "Overall mood or tone of the creative",
                                  "example": "upbeat, energetic",
                                  "type": "string"
                                },
                                "quality": {
                                  "description": "Production quality assessment.\nPossible values: professional, semi-professional, amateur.\n",
                                  "example": "professional",
                                  "type": "string"
                                },
                                "setting": {
                                  "description": "Detected setting or environment",
                                  "example": "beachfront retail store",
                                  "type": "string"
                                },
                                "subjects": {
                                  "description": "Primary subjects identified in the creative",
                                  "example": [
                                    "beach",
                                    "summer clothing",
                                    "retail store"
                                  ],
                                  "items": {
                                    "type": "string"
                                  },
                                  "type": "array"
                                }
                              },
                              "type": "object"
                            },
                            "brand": {
                              "description": "Detected brand name (null if no known brand identified)",
                              "example": "Acme Retail",
                              "nullable": true,
                              "type": "string"
                            },
                            "category": {
                              "description": "Primary content category",
                              "example": "Retail/Fashion",
                              "nullable": true,
                              "type": "string"
                            },
                            "content_classification": {
                              "description": "Detailed content classification with individual boolean flags\nfor each content category, age rating, brand detection, and\nconfidence score.\n",
                              "properties": {
                                "age_rating": {
                                  "description": "MPAA-style age rating assigned by AI moderation.\nScreens can be configured to only show creatives up to\na certain rating based on venue type.\n",
                                  "enum": [
                                    "G",
                                    "PG",
                                    "PG-13",
                                    "R",
                                    "NC-17"
                                  ],
                                  "example": "G",
                                  "type": "string"
                                },
                                "confidence": {
                                  "description": "Overall classification confidence score (0.0 to 1.0).\nScores below 0.7 may trigger manual review.\n",
                                  "example": 0.94,
                                  "maximum": 1,
                                  "minimum": 0,
                                  "type": "number"
                                },
                                "contains_adult_content": {
                                  "description": "Whether the creative contains adult/sexual content",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "contains_alcohol": {
                                  "description": "Whether the creative contains alcohol-related content",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "contains_firearms": {
                                  "description": "Whether the creative depicts firearms",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "contains_gambling": {
                                  "description": "Whether the creative contains gambling-related content",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "contains_political": {
                                  "description": "Whether the creative contains political content",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "contains_profanity": {
                                  "description": "Whether the creative contains profane language",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "contains_tobacco": {
                                  "description": "Whether the creative contains tobacco-related content",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "contains_violence": {
                                  "description": "Whether the creative depicts violence",
                                  "example": false,
                                  "type": "boolean"
                                },
                                "is_known_brand": {
                                  "description": "Whether the AI detected a known brand in the creative",
                                  "example": true,
                                  "type": "boolean"
                                }
                              },
                              "type": "object"
                            },
                            "flags": {
                              "description": "Content flags raised during moderation. Empty array means\nno flags were raised. Possible values: alcohol, tobacco,\ngambling, political, violence, profanity, adult_content, firearms.\n",
                              "example": [],
                              "items": {
                                "type": "string"
                              },
                              "type": "array"
                            },
                            "reviewed_at": {
                              "description": "When moderation completed",
                              "example": "2026-04-02T10:16:02.000Z",
                              "format": "date-time",
                              "nullable": true,
                              "type": "string"
                            },
                            "status": {
                              "description": "Moderation pipeline status",
                              "enum": [
                                "pending",
                                "approved",
                                "rejected"
                              ],
                              "example": "approved",
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "name": {
                          "description": "Creative name",
                          "example": "Summer Sale 15s Video",
                          "type": "string"
                        },
                        "openrtb": {
                          "description": "OpenRTB-compatible fields for programmatic targeting",
                          "properties": {
                            "cat": {
                              "description": "IAB Content Taxonomy 1.0 category codes. These codes are\nused for blocklist matching in programmatic auctions.\n",
                              "example": [
                                "IAB18",
                                "IAB22-1"
                              ],
                              "items": {
                                "type": "string"
                              },
                              "type": "array"
                            }
                          },
                          "type": "object"
                        },
                        "status": {
                          "description": "Current creative status",
                          "enum": [
                            "pending_moderation",
                            "approved",
                            "rejected",
                            "active",
                            "deactivated"
                          ],
                          "example": "approved",
                          "type": "string"
                        },
                        "updated_at": {
                          "description": "Last update timestamp (including moderation updates)",
                          "example": "2026-04-02T10:16:02.000Z",
                          "format": "date-time",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Creative details with full moderation results",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Creative not found",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Creative not found or does not belong to your DSP"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get creative details with full moderation results",
        "tags": [
          "Creatives"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/creatives/6620abc123def456789abcde\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/creatives/{id}/confirm": {
      "post": {
        "description": "After uploading a creative via the presigned URL (upload mode), call this endpoint\nto confirm the upload and trigger AI moderation.\n\n**This endpoint is only needed for upload mode** (when you provided `filename`\ninstead of `creative_url` during creation). URL mode creatives are moderated\nautomatically.\n\nNo request body is required. The endpoint verifies the file exists in S3 and\nenqueues the AI moderation pipeline.\n\n**Rate limit:** 100 requests/minute.\n",
        "operationId": "confirmCreativeUpload",
        "parameters": [
          {
            "description": "Creative ID returned from `POST /creatives`",
            "example": "6620abc123def456789abcde",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "creative_id": "6620abc123def456789abcde",
                    "moderation": {
                      "description": "Upload confirmed. AI moderation queued. Poll GET /creatives/6620abc123def456789abcde for results (~30 seconds).",
                      "status": "queued"
                    },
                    "status": "pending_moderation"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "creative_id": {
                          "description": "Creative ID",
                          "example": "6620abc123def456789abcde",
                          "type": "string"
                        },
                        "moderation": {
                          "properties": {
                            "description": {
                              "example": "Upload confirmed. AI moderation queued. Poll GET /creatives/6620abc123def456789abcde for results (~30 seconds).",
                              "type": "string"
                            },
                            "status": {
                              "example": "queued",
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "status": {
                          "description": "Updated creative status",
                          "example": "pending_moderation",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Upload confirmed and moderation triggered",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Creative not found",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Creative not found or does not belong to your DSP"
          },
          "422": {
            "content": {
              "application/json": {
                "example": {
                  "error": "File not found at upload URL. Upload the file before confirming.",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Upload not found. The file has not been uploaded to the presigned URL yet,\nor the presigned URL has expired.\n"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Confirm presigned upload and trigger moderation",
        "tags": [
          "Creatives"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/creatives/6620abc123def456789abcde/confirm\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/deals": {
      "get": {
        "description": "Returns all active deals available to the authenticated DSP seat. Deals are\nfiltered by your seat ID — you only see deals where your DSP has been\nexplicitly invited or deals that are open to all registered DSPs.\n\n**Deal types:**\n- **PMP** — Private marketplace deal with floor price\n- **Preferred** — Preferred deal with fixed pricing and priority\n- **Guaranteed** — Guaranteed delivery with committed impression volume\n",
        "operationId": "listDeals",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "created_at": "2026-03-28T09:00:00.000Z",
                      "deal_id": "deal_retail_q2_2026",
                      "deal_type": "pmp",
                      "eligible_countries": [
                        "US",
                        "CA"
                      ],
                      "eligible_venue_types": [
                        "retail",
                        "restaurant"
                      ],
                      "end_date": "2026-06-30",
                      "floor_cpm": 5,
                      "impression_cap": 500000,
                      "impressions_delivered": 127430,
                      "start_date": "2026-04-01",
                      "status": "active"
                    },
                    {
                      "created_at": "2026-03-14T16:30:00.000Z",
                      "deal_id": "deal_transit_premium_2026",
                      "deal_type": "preferred",
                      "eligible_countries": [
                        "US"
                      ],
                      "eligible_venue_types": [
                        "transit",
                        "airport"
                      ],
                      "end_date": null,
                      "floor_cpm": 12,
                      "impression_cap": null,
                      "impressions_delivered": 45200,
                      "start_date": "2026-03-15",
                      "status": "active"
                    }
                  ],
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "description": "Array of active deals",
                      "items": {
                        "properties": {
                          "created_at": {
                            "description": "When the deal was created",
                            "example": "2026-03-28T09:00:00.000Z",
                            "format": "date-time",
                            "type": "string"
                          },
                          "deal_id": {
                            "description": "Unique deal identifier (use in bid.dealid)",
                            "example": "deal_retail_q2_2026",
                            "type": "string"
                          },
                          "deal_type": {
                            "description": "Type of deal",
                            "enum": [
                              "pmp",
                              "preferred",
                              "guaranteed"
                            ],
                            "example": "pmp",
                            "type": "string"
                          },
                          "eligible_countries": {
                            "description": "Countries this deal applies to (null = all countries)",
                            "example": [
                              "US",
                              "CA"
                            ],
                            "items": {
                              "type": "string"
                            },
                            "nullable": true,
                            "type": "array"
                          },
                          "eligible_venue_types": {
                            "description": "Venue types this deal applies to (null = all venues)",
                            "example": [
                              "retail",
                              "restaurant"
                            ],
                            "items": {
                              "type": "string"
                            },
                            "nullable": true,
                            "type": "array"
                          },
                          "end_date": {
                            "description": "Deal end date (null = no expiration)",
                            "example": "2026-06-30",
                            "format": "date",
                            "nullable": true,
                            "type": "string"
                          },
                          "floor_cpm": {
                            "description": "Minimum CPM price in USD",
                            "example": 5,
                            "type": "number"
                          },
                          "impression_cap": {
                            "description": "Maximum impressions for this deal (null = uncapped)",
                            "example": 500000,
                            "nullable": true,
                            "type": "integer"
                          },
                          "impressions_delivered": {
                            "description": "Impressions delivered so far",
                            "example": 127430,
                            "type": "integer"
                          },
                          "start_date": {
                            "description": "Deal start date",
                            "example": "2026-04-01",
                            "format": "date",
                            "type": "string"
                          },
                          "status": {
                            "description": "Deal status",
                            "enum": [
                              "active",
                              "paused",
                              "expired"
                            ],
                            "example": "active",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "List of active deals",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List active deals for your DSP",
        "tags": [
          "Deals"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/deals\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/deals/propose": {
      "post": {
        "description": "Propose a new PMP, preferred, or guaranteed deal. Proposed deals are created in\n`pending` status and require admin approval before becoming active.\n\n**Required:** `deal_id` (your unique identifier) and `floor_cpm` (minimum CPM > 0).\n\n**Deal lifecycle:** pending -> active -> (paused | expired)\n\nYou will receive a webhook notification (if configured) when the deal status changes.\n\n**Rate limit:** 100 requests/minute.\n",
        "operationId": "proposeDeal",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "deal_id": "deal_acme_summer_retail",
                "deal_type": "pmp",
                "eligible_countries": [
                  "US"
                ],
                "eligible_venue_types": [
                  "retail",
                  "restaurant",
                  "coffee_shop"
                ],
                "end_date": "2026-07-10",
                "floor_cpm": 8,
                "impression_cap": 250000,
                "start_date": "2026-04-10"
              },
              "schema": {
                "properties": {
                  "deal_id": {
                    "description": "Your unique deal identifier. This becomes the `dealid` field in\nOpenRTB bid requests. Use a descriptive, URL-safe string.\n",
                    "example": "deal_acme_summer_retail",
                    "maxLength": 128,
                    "minLength": 2,
                    "type": "string"
                  },
                  "deal_type": {
                    "default": "pmp",
                    "description": "Type of deal. Defaults to \"pmp\" if not specified.\n- `pmp` — Private marketplace with floor price\n- `preferred` — Fixed price, priority over open auction\n- `guaranteed` — Committed volume at fixed price\n",
                    "enum": [
                      "pmp",
                      "preferred",
                      "guaranteed"
                    ],
                    "example": "pmp",
                    "type": "string"
                  },
                  "eligible_countries": {
                    "description": "ISO 3166-1 alpha-2 country codes this deal applies to. Null means\nall countries.\n",
                    "example": [
                      "US"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "nullable": true,
                    "type": "array"
                  },
                  "eligible_venue_types": {
                    "description": "Venue types this deal applies to. Null means all venue types.\n",
                    "example": [
                      "retail",
                      "restaurant",
                      "coffee_shop"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "nullable": true,
                    "type": "array"
                  },
                  "end_date": {
                    "description": "Deal end date (YYYY-MM-DD). Null for no expiration.",
                    "example": "2026-07-10",
                    "format": "date",
                    "nullable": true,
                    "type": "string"
                  },
                  "floor_cpm": {
                    "description": "Minimum CPM price in USD. All bids under this deal must meet or\nexceed this floor price.\n",
                    "example": 8,
                    "minimum": 0.01,
                    "type": "number"
                  },
                  "impression_cap": {
                    "description": "Maximum impressions for this deal. Set to null for uncapped.\nGuaranteed deals should always have an impression cap.\n",
                    "example": 250000,
                    "minimum": 1,
                    "nullable": true,
                    "type": "integer"
                  },
                  "start_date": {
                    "description": "Deal start date (YYYY-MM-DD). Defaults to today.",
                    "example": "2026-04-10",
                    "format": "date",
                    "type": "string"
                  }
                },
                "required": [
                  "deal_id",
                  "floor_cpm"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "created_at": "2026-04-03T14:30:00.000Z",
                    "deal_id": "deal_acme_summer_retail",
                    "deal_type": "pmp",
                    "eligible_countries": [
                      "US"
                    ],
                    "eligible_venue_types": [
                      "retail",
                      "restaurant",
                      "coffee_shop"
                    ],
                    "end_date": "2026-07-10",
                    "floor_cpm": 8,
                    "impression_cap": 250000,
                    "start_date": "2026-04-10",
                    "status": "pending"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "created_at": {
                          "example": "2026-04-03T14:30:00.000Z",
                          "format": "date-time",
                          "type": "string"
                        },
                        "deal_id": {
                          "description": "Your deal identifier",
                          "example": "deal_acme_summer_retail",
                          "type": "string"
                        },
                        "deal_type": {
                          "example": "pmp",
                          "type": "string"
                        },
                        "eligible_countries": {
                          "example": [
                            "US"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "nullable": true,
                          "type": "array"
                        },
                        "eligible_venue_types": {
                          "example": [
                            "retail",
                            "restaurant",
                            "coffee_shop"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "nullable": true,
                          "type": "array"
                        },
                        "end_date": {
                          "example": "2026-07-10",
                          "format": "date",
                          "nullable": true,
                          "type": "string"
                        },
                        "floor_cpm": {
                          "example": 8,
                          "type": "number"
                        },
                        "impression_cap": {
                          "example": 250000,
                          "nullable": true,
                          "type": "integer"
                        },
                        "start_date": {
                          "example": "2026-04-10",
                          "format": "date",
                          "type": "string"
                        },
                        "status": {
                          "description": "All proposed deals start as \"pending\". Admin will review\nand activate.\n",
                          "example": "pending",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Deal proposed successfully. The deal is created in `pending` status and\nrequires admin approval before becoming active.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "deal_id and floor_cpm are required. floor_cpm must be greater than 0.",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request parameters"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Deal ID \"deal_acme_summer_retail\" already exists",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Deal ID already exists"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Propose a new deal",
        "tags": [
          "Deals"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/deals/propose\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"deal_id\":\"deal_acme_summer_retail\",\"floor_cpm\":8,\"deal_type\":\"pmp\",\"impression_cap\":250000,\"start_date\":\"2026-04-10\",\"end_date\":\"2026-07-10\",\"eligible_venue_types\":[\"retail\",\"restaurant\",\"coffee_shop\"],\"eligible_countries\":[\"US\"]}'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/deals/{dealId}": {
      "get": {
        "description": "Returns full details for a specific deal, including impression delivery\nprogress, eligible venues and countries, and pricing terms.\n",
        "operationId": "getDeal",
        "parameters": [
          {
            "description": "Deal identifier",
            "example": "deal_retail_q2_2026",
            "in": "path",
            "name": "dealId",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "created_at": "2026-03-28T09:00:00.000Z",
                    "deal_id": "deal_retail_q2_2026",
                    "deal_type": "pmp",
                    "eligible_countries": [
                      "US",
                      "CA"
                    ],
                    "eligible_venue_types": [
                      "retail",
                      "restaurant"
                    ],
                    "end_date": "2026-06-30",
                    "floor_cpm": 5,
                    "impression_cap": 500000,
                    "impressions_delivered": 127430,
                    "start_date": "2026-04-01",
                    "status": "active",
                    "updated_at": "2026-04-03T12:00:00.000Z"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "created_at": {
                          "example": "2026-03-28T09:00:00.000Z",
                          "format": "date-time",
                          "type": "string"
                        },
                        "deal_id": {
                          "description": "Unique deal identifier",
                          "example": "deal_retail_q2_2026",
                          "type": "string"
                        },
                        "deal_type": {
                          "enum": [
                            "pmp",
                            "preferred",
                            "guaranteed"
                          ],
                          "example": "pmp",
                          "type": "string"
                        },
                        "eligible_countries": {
                          "example": [
                            "US",
                            "CA"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "nullable": true,
                          "type": "array"
                        },
                        "eligible_venue_types": {
                          "example": [
                            "retail",
                            "restaurant"
                          ],
                          "items": {
                            "type": "string"
                          },
                          "nullable": true,
                          "type": "array"
                        },
                        "end_date": {
                          "example": "2026-06-30",
                          "format": "date",
                          "nullable": true,
                          "type": "string"
                        },
                        "floor_cpm": {
                          "description": "Floor CPM in USD",
                          "example": 5,
                          "type": "number"
                        },
                        "impression_cap": {
                          "description": "Max impressions (null = uncapped)",
                          "example": 500000,
                          "nullable": true,
                          "type": "integer"
                        },
                        "impressions_delivered": {
                          "description": "Impressions delivered against this deal",
                          "example": 127430,
                          "type": "integer"
                        },
                        "start_date": {
                          "example": "2026-04-01",
                          "format": "date",
                          "type": "string"
                        },
                        "status": {
                          "enum": [
                            "active",
                            "paused",
                            "expired",
                            "pending"
                          ],
                          "example": "active",
                          "type": "string"
                        },
                        "updated_at": {
                          "example": "2026-04-03T12:00:00.000Z",
                          "format": "date-time",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Deal details",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Deal deal_retail_q2_2026 not found",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Deal not found or not available to your DSP seat"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get deal details",
        "tags": [
          "Deals"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/deals/deal_retail_q2_2026\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/discover": {
      "post": {
        "description": "Find screens using natural language queries. Uses **pgvector semantic embeddings**\nto match your query against scene descriptions generated by on-device vision\nlanguage models (VLMs).\n\n**Example queries:**\n- \"busy coffee shops with young professionals in Manhattan\"\n- \"gym lobbies with TVs near college campuses\"\n- \"high-end restaurants in Beverly Hills with bar seating\"\n- \"transit hubs with high foot traffic during rush hour\"\n\nResults are ranked by cosine similarity to your query embedding. Each result\nincludes screen details and FEIN signals (when available).\n\nOptionally provide a `budget` to receive delivery estimates alongside results.\n\n**Note:** When the semantic search backend (pgvector) is unavailable, the\nresponse is simplified — results are returned without similarity scores and\nthe `fein` object is omitted from each result. The endpoint still returns\nmatching screens based on keyword/filter fallback.\n",
        "operationId": "discover",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "budget": 2500,
                "limit": 20,
                "query": "busy coffee shops with young professionals in NYC"
              },
              "schema": {
                "properties": {
                  "budget": {
                    "description": "Optional budget in USD. If provided, each result includes a delivery\nestimate for that screen based on historical data.\n",
                    "example": 2500,
                    "minimum": 0.01,
                    "type": "number"
                  },
                  "limit": {
                    "default": 20,
                    "description": "Maximum number of results to return. Default is 20, maximum is 50.\nResults are ordered by semantic similarity score descending.\n",
                    "example": 20,
                    "maximum": 50,
                    "minimum": 1,
                    "type": "integer"
                  },
                  "query": {
                    "description": "Natural language description of your desired inventory. Be as\nspecific as possible — include venue type, audience demographics,\ngeography, and any other criteria. The query is embedded using\nthe same model that encodes scene descriptions from on-device VLMs.\n",
                    "example": "busy coffee shops with young professionals in NYC",
                    "maxLength": 500,
                    "minLength": 3,
                    "type": "string"
                  }
                },
                "required": [
                  "query"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "query": "busy coffee shops with young professionals in NYC",
                  "results": [
                    {
                      "city": "New York",
                      "country": "US",
                      "fein": {
                        "ad_receptivity": 0.71,
                        "attention_score": 0.65,
                        "crowd_density": 28,
                        "data_quality": "live",
                        "dominant_emotion": "neutral",
                        "dwell_time_ms": 38000,
                        "income_level": "high",
                        "last_updated": "2026-04-03T14:18:45.000Z",
                        "live_face_count": 8,
                        "purchase_intent": "medium",
                        "vas_7d": 6.2
                      },
                      "name": "Blue Bottle Coffee - SoHo Counter",
                      "screen_id": "scr_4a8b2c1d3e5f6a7b",
                      "venue_type": "coffee_shop"
                    },
                    {
                      "city": "New York",
                      "country": "US",
                      "fein": {
                        "ad_receptivity": 0.63,
                        "attention_score": 0.58,
                        "crowd_density": 18,
                        "data_quality": "live",
                        "dominant_emotion": "happy",
                        "dwell_time_ms": 52000,
                        "income_level": "premium",
                        "last_updated": "2026-04-03T14:19:02.000Z",
                        "live_face_count": 5,
                        "purchase_intent": "low",
                        "vas_7d": 5.1
                      },
                      "name": "Stumptown Coffee - Chelsea",
                      "screen_id": "scr_9c7d6e5f4a3b2c1d",
                      "venue_type": "coffee_shop"
                    }
                  ],
                  "total": 34
                },
                "schema": {
                  "properties": {
                    "query": {
                      "description": "Echo of the original query",
                      "example": "busy coffee shops with young professionals in NYC",
                      "type": "string"
                    },
                    "results": {
                      "description": "Screens matching the query, ordered by cosine similarity score.\nEach result includes screen details and FEIN signals when available.\n",
                      "items": {
                        "properties": {
                          "city": {
                            "description": "City where the screen is located",
                            "example": "New York",
                            "nullable": true,
                            "type": "string"
                          },
                          "country": {
                            "description": "ISO 3166-1 alpha-2 country code",
                            "example": "US",
                            "type": "string"
                          },
                          "fein": {
                            "description": "FEIN signals (null if screen has no FEIN sensors)",
                            "nullable": true,
                            "properties": {
                              "ad_receptivity": {
                                "example": 0.71,
                                "maximum": 1,
                                "minimum": 0,
                                "nullable": true,
                                "type": "number"
                              },
                              "attention_score": {
                                "description": "Average gaze attention (0-1)",
                                "example": 0.65,
                                "maximum": 1,
                                "minimum": 0,
                                "type": "number"
                              },
                              "crowd_density": {
                                "example": 28,
                                "nullable": true,
                                "type": "integer"
                              },
                              "data_quality": {
                                "enum": [
                                  "live",
                                  "historical",
                                  "none"
                                ],
                                "example": "live",
                                "type": "string"
                              },
                              "dominant_emotion": {
                                "example": "neutral",
                                "nullable": true,
                                "type": "string"
                              },
                              "dwell_time_ms": {
                                "description": "Average dwell time in milliseconds",
                                "example": 38000,
                                "type": "integer"
                              },
                              "income_level": {
                                "enum": [
                                  "low",
                                  "medium",
                                  "high",
                                  "premium"
                                ],
                                "example": "high",
                                "nullable": true,
                                "type": "string"
                              },
                              "last_updated": {
                                "example": "2026-04-03T14:18:45.000Z",
                                "format": "date-time",
                                "nullable": true,
                                "type": "string"
                              },
                              "live_face_count": {
                                "description": "People currently facing the screen",
                                "example": 8,
                                "type": "integer"
                              },
                              "purchase_intent": {
                                "example": "medium",
                                "nullable": true,
                                "type": "string"
                              },
                              "vas_7d": {
                                "description": "Verified Attention Seconds (7-day average)",
                                "example": 6.2,
                                "nullable": true,
                                "type": "number"
                              }
                            },
                            "type": "object"
                          },
                          "name": {
                            "description": "Human-readable screen name",
                            "example": "Blue Bottle Coffee - SoHo Counter",
                            "type": "string"
                          },
                          "screen_id": {
                            "description": "Trillboards screen identifier",
                            "example": "scr_4a8b2c1d3e5f6a7b",
                            "type": "string"
                          },
                          "venue_type": {
                            "description": "Venue classification",
                            "example": "coffee_shop",
                            "nullable": true,
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "total": {
                      "description": "Total number of matching screens (may exceed limit)",
                      "example": 34,
                      "type": "integer"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Matching screens ranked by semantic similarity",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "query is required and must be a non-empty string",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request — the `query` field is required"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Semantic inventory search",
        "tags": [
          "Inventory"
        ],
        "x-beta": true,
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/discover\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"query\":\"busy coffee shops with young professionals in NYC\",\"budget\":2500,\"limit\":20}'\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/domedia/availability": {
      "post": {
        "description": "DOmedia OpenDirect callback over the existing inventory and pricing owners.\nRequires a platform key owned by data_partner/domedia with inventory:read.\nProduct IDs are DOmedia asset IDs. UTC flight boundaries are continuous,\nstart-inclusive and end-exclusive. Price is USD for the available flight.\nAssets without positive available capacity are omitted. A missing canonical\nprice omits price without erasing known availability.\nThe existing data API limiter allows 60 authenticated requests per minute\nper partner; the callback shares that counter and its rate-limit response.\n",
        "operationId": "getDomediaInventoryAvailability",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "currency": {
                    "enum": [
                      "USD"
                    ],
                    "type": "string"
                  },
                  "enddate": {
                    "description": "Must follow startdate.",
                    "format": "date-time",
                    "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$",
                    "type": "string"
                  },
                  "productids": {
                    "description": "Provider asset IDs; repeated IDs are deduplicated.",
                    "items": {
                      "pattern": "^[1-9][0-9]*$",
                      "type": "string"
                    },
                    "maxItems": 5000,
                    "minItems": 1,
                    "type": "array"
                  },
                  "startdate": {
                    "format": "date-time",
                    "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$",
                    "type": "string"
                  }
                },
                "required": [
                  "productids",
                  "startdate",
                  "enddate"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "avails": {
                      "items": {
                        "properties": {
                          "availsstatus": {
                            "items": {
                              "properties": {
                                "status": {
                                  "enum": [
                                    "Available",
                                    "PartiallyAvailable",
                                    "Unavailable"
                                  ],
                                  "type": "string"
                                }
                              },
                              "required": [
                                "status"
                              ],
                              "type": "object"
                            },
                            "minItems": 1,
                            "type": "array"
                          },
                          "currency": {
                            "enum": [
                              "USD"
                            ],
                            "type": "string"
                          },
                          "enddate": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "price": {
                            "description": "USD for the available requested flight.",
                            "minimum": 0,
                            "type": "number"
                          },
                          "productid": {
                            "type": "string"
                          },
                          "startdate": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "required": [
                          "productid",
                          "currency",
                          "startdate",
                          "enddate",
                          "availsstatus"
                        ],
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "avails"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Available canonical inventory and optional USD flight prices.",
            "headers": {
              "Cache-Control": {
                "schema": {
                  "enum": [
                    "no-store"
                  ],
                  "type": "string"
                }
              },
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window.",
                "schema": {
                  "minimum": 0,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window.",
                "schema": {
                  "minimum": 0,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the current rate-limit window resets.",
                "schema": {
                  "minimum": 0,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrillboardsError"
                }
              }
            },
            "description": "Invalid product IDs, flight boundaries or currency."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrillboardsError"
                }
              }
            },
            "description": "Missing, invalid or revoked platform credential."
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrillboardsError"
                }
              }
            },
            "description": "Wrong owner, capability or unsupported credential constraint."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "retryAfter": {
                      "minimum": 0,
                      "type": "integer"
                    },
                    "success": {
                      "enum": [
                        false
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "message",
                    "retryAfter"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Shared inventory request limit exceeded (60 per minute)."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrillboardsError"
                }
              }
            },
            "description": "Inventory connection disabled or temporarily unavailable."
          }
        },
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Quote mapped DOmedia inventory availability",
        "tags": [
          "Inventory"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/fein/audience-segments": {
      "get": {
        "operationId": "getFeinAudienceSegments",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "meta": {
                      "properties": {
                        "segment_count": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "scene_embeddings": {
                      "properties": {
                        "embeddings_24h": {
                          "type": "integer"
                        },
                        "total_embeddings": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "venue_segments": {
                      "items": {
                        "properties": {
                          "avg_attention": {
                            "type": "number"
                          },
                          "avg_face_count": {
                            "type": "number"
                          },
                          "avg_receptivity": {
                            "type": "number"
                          },
                          "screen_count": {
                            "type": "integer"
                          },
                          "segment_name": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "FEIN audience segments"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get FEIN audience segments",
        "tags": [
          "FEIN Analytics"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/content-audience-correlation": {
      "get": {
        "description": "Returns a breakdown of content categories crossed with venue types,\nshowing audience attention, face count, dwell time, and ad receptivity\nfor each combination. Useful for understanding which content types\ndrive the best audience engagement in which venue contexts.\n",
        "operationId": "getContentAudienceCorrelation",
        "parameters": [
          {
            "description": "Optional content category filter",
            "in": "query",
            "name": "content_category",
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Lookback window in days",
            "in": "query",
            "name": "days",
            "schema": {
              "default": 30,
              "maximum": 90,
              "type": "integer"
            }
          },
          {
            "description": "Optional venue type filter",
            "in": "query",
            "name": "venue_type",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "avgAttention": {
                            "nullable": true,
                            "type": "number"
                          },
                          "avgDwellMs": {
                            "nullable": true,
                            "type": "number"
                          },
                          "avgFaceCount": {
                            "nullable": true,
                            "type": "number"
                          },
                          "avgReceptivity": {
                            "nullable": true,
                            "type": "number"
                          },
                          "contentCategory": {
                            "type": "string"
                          },
                          "totalPlays": {
                            "type": "integer"
                          },
                          "venueType": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "meta": {
                      "properties": {
                        "content_category": {
                          "nullable": true,
                          "type": "string"
                        },
                        "count": {
                          "type": "integer"
                        },
                        "days": {
                          "type": "integer"
                        },
                        "venue_type": {
                          "nullable": true,
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Content audience correlation data"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Content category x audience demographic breakdown",
        "tags": [
          "Content Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/content-performance/{videoId}": {
      "get": {
        "description": "Returns aggregated performance data for a single piece of content,\nincluding play counts, audience attention metrics, venue distribution,\ndaypart distribution, and top-performing screens.\n",
        "operationId": "getContentPerformance",
        "parameters": [
          {
            "description": "YouTube video ID or stream identifier",
            "in": "path",
            "name": "videoId",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Lookback window in days",
            "in": "query",
            "name": "days",
            "schema": {
              "default": 30,
              "maximum": 90,
              "type": "integer"
            }
          },
          {
            "description": "Optional venue type filter",
            "in": "query",
            "name": "venue_type",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "avgAttention": {
                      "description": "Average attention score (0.0-1.0)",
                      "type": "number"
                    },
                    "avgAudienceSize": {
                      "description": "Average face count during playback",
                      "type": "number"
                    },
                    "avgDwellMs": {
                      "description": "Average dwell time in milliseconds",
                      "type": "number"
                    },
                    "daypartDistribution": {
                      "items": {
                        "properties": {
                          "daypart": {
                            "enum": [
                              "morning",
                              "afternoon",
                              "evening",
                              "overnight"
                            ],
                            "type": "string"
                          },
                          "plays": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "period": {
                      "properties": {
                        "end": {
                          "format": "date",
                          "type": "string"
                        },
                        "start": {
                          "format": "date",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "title": {
                      "nullable": true,
                      "type": "string"
                    },
                    "topScreens": {
                      "items": {
                        "properties": {
                          "avgAttention": {
                            "nullable": true,
                            "type": "number"
                          },
                          "plays": {
                            "type": "integer"
                          },
                          "screenId": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "totalPlays": {
                      "type": "integer"
                    },
                    "uniqueScreens": {
                      "type": "integer"
                    },
                    "venueDistribution": {
                      "items": {
                        "properties": {
                          "plays": {
                            "type": "integer"
                          },
                          "venue_type": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "videoId": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Content performance data"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Missing or invalid videoId parameter"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get content performance by video ID",
        "tags": [
          "Content Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/content-recommendations": {
      "get": {
        "description": "Returns content recommendations ranked by a composite performance score\n(attention * 0.4 + replay density * 0.3 + dwell time * 0.3) for a given\nvenue type and optional daypart. Requires at least 3 plays to qualify.\n",
        "operationId": "getContentRecommendations",
        "parameters": [
          {
            "description": "Venue type to get recommendations for",
            "in": "query",
            "name": "venue_type",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional daypart filter",
            "in": "query",
            "name": "daypart",
            "schema": {
              "enum": [
                "morning",
                "afternoon",
                "evening",
                "overnight"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 10,
              "maximum": 100,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "avgAttention": {
                            "nullable": true,
                            "type": "number"
                          },
                          "avgDwellMs": {
                            "nullable": true,
                            "type": "number"
                          },
                          "contentCategory": {
                            "nullable": true,
                            "type": "string"
                          },
                          "durationSeconds": {
                            "nullable": true,
                            "type": "number"
                          },
                          "performanceScore": {
                            "description": "Composite score (0.0-1.0)",
                            "type": "number"
                          },
                          "title": {
                            "nullable": true,
                            "type": "string"
                          },
                          "totalPlays": {
                            "type": "integer"
                          },
                          "uniqueScreens": {
                            "type": "integer"
                          },
                          "videoId": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "meta": {
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "daypart": {
                          "nullable": true,
                          "type": "string"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "venue_type": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Content recommendations"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Missing venue_type parameter"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get best-performing content for a venue context",
        "tags": [
          "Content Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/content-semantic-search": {
      "post": {
        "description": "Accepts a natural language query, embeds it via the embedding service,\nand returns content_reviews ordered by cosine similarity using pgvector.\nRequires the content_reviews table to have embeddings populated.\n",
        "operationId": "contentSemanticSearch",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "limit": {
                    "default": 20,
                    "maximum": 100,
                    "type": "integer"
                  },
                  "minSimilarity": {
                    "default": 0.5,
                    "description": "Minimum cosine similarity threshold",
                    "maximum": 1,
                    "minimum": 0,
                    "type": "number"
                  },
                  "query": {
                    "description": "Natural language search query",
                    "example": "family friendly cooking content",
                    "minLength": 3,
                    "type": "string"
                  }
                },
                "required": [
                  "query"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "contentCategory": {
                            "nullable": true,
                            "type": "string"
                          },
                          "description": {
                            "nullable": true,
                            "type": "string"
                          },
                          "durationSeconds": {
                            "nullable": true,
                            "type": "number"
                          },
                          "reviewStatus": {
                            "type": "string"
                          },
                          "similarity": {
                            "description": "Cosine similarity score (0.0-1.0)",
                            "type": "number"
                          },
                          "title": {
                            "nullable": true,
                            "type": "string"
                          },
                          "videoId": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "meta": {
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "minSimilarity": {
                          "type": "number"
                        },
                        "query": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Semantically similar content"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Missing or too-short query"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Semantic similarity search over content library",
        "tags": [
          "Content Intelligence"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/creative-venue-performance": {
      "get": {
        "operationId": "getCreativeVenuePerformance",
        "parameters": [
          {
            "in": "query",
            "name": "venue_type",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "days",
            "schema": {
              "default": 30,
              "maximum": 90,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "min_plays",
            "schema": {
              "default": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 100,
              "maximum": 1000,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "avg_attention": {
                            "type": "number"
                          },
                          "avg_attention_all": {
                            "type": "number"
                          },
                          "avg_cpm": {
                            "type": "number"
                          },
                          "brand": {
                            "type": "string"
                          },
                          "creative_category": {
                            "type": "string"
                          },
                          "day": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "inference_source_count": {
                            "type": "integer"
                          },
                          "plays": {
                            "type": "integer"
                          },
                          "unique_creatives": {
                            "type": "integer"
                          },
                          "venue_type": {
                            "type": "string"
                          },
                          "win_rate": {
                            "type": "number"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "meta": {
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "days": {
                          "type": "integer"
                        },
                        "min_plays": {
                          "type": "integer"
                        },
                        "venue_type": {
                          "nullable": true,
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Creative venue performance matrix"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get creative x venue performance matrix",
        "tags": [
          "FEIN Analytics"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/fill-rate-by-venue": {
      "get": {
        "operationId": "getFillRateByVenue",
        "parameters": [
          {
            "in": "query",
            "name": "venue_type",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "daypart",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "days",
            "schema": {
              "default": 30,
              "maximum": 90,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 100,
              "maximum": 1000,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "avg_cpm": {
                            "type": "number"
                          },
                          "daypart": {
                            "type": "string"
                          },
                          "fill_rate": {
                            "type": "number"
                          },
                          "fills": {
                            "type": "integer"
                          },
                          "hour": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "total_requests": {
                            "type": "integer"
                          },
                          "venue_type": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "meta": {
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "daypart": {
                          "nullable": true,
                          "type": "string"
                        },
                        "days": {
                          "type": "integer"
                        },
                        "venue_type": {
                          "nullable": true,
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Fill rate by venue"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get fill rate by venue",
        "tags": [
          "FEIN Analytics"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/inference-history": {
      "get": {
        "operationId": "getFeinInferenceHistory",
        "parameters": [
          {
            "in": "query",
            "name": "screen_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Start time (default 24h ago)",
            "in": "query",
            "name": "start",
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "End time (default now)",
            "in": "query",
            "name": "end",
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 100,
              "maximum": 1000,
              "type": "integer"
            }
          },
          {
            "description": "Filter by inference source",
            "in": "query",
            "name": "source",
            "schema": {
              "enum": [
                "device",
                "synthetic"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "properties": {
                          "attention_score": {
                            "type": "number"
                          },
                          "bid_outcome": {
                            "type": "string"
                          },
                          "body_engagement": {
                            "type": "number"
                          },
                          "clearing_cpm": {
                            "type": "number"
                          },
                          "creative_id": {
                            "type": "string"
                          },
                          "daypart": {
                            "type": "string"
                          },
                          "emotion_score": {
                            "type": "number"
                          },
                          "face_count": {
                            "type": "integer"
                          },
                          "focus_region": {
                            "description": "Primary focus region within a 3x3 attention grid (0-8, 4 = centre), from the on-device FaceLandmarker. NULL when no FaceLandmarker verdict was produced for the window, which on the ML-Kit-only fleet is almost every window. Was previously defaulted to 4 — see inference_outcomes.signal_coverage for why a value is absent.",
                            "nullable": true,
                            "type": "string"
                          },
                          "inference_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "inference_snapshot_id": {
                            "format": "uuid",
                            "type": "string"
                          },
                          "inference_source": {
                            "type": "string"
                          },
                          "venue_type": {
                            "type": "string"
                          },
                          "weather_code": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "meta": {
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "end": {
                          "format": "date-time",
                          "type": "string"
                        },
                        "screen_id": {
                          "type": "string"
                        },
                        "source": {
                          "nullable": true,
                          "type": "string"
                        },
                        "start": {
                          "format": "date-time",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "FEIN inference history"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get FEIN inference history",
        "tags": [
          "FEIN Analytics"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/model-status": {
      "get": {
        "operationId": "getFeinModelStatus",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gradient_uploads": {
                      "properties": {
                        "last_upload": {
                          "format": "date-time",
                          "nullable": true,
                          "type": "string"
                        },
                        "total_uploads": {
                          "type": "integer"
                        },
                        "uploads_24h": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "meta": {
                      "properties": {
                        "total_slices": {
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "model_slices": {
                      "items": {
                        "properties": {
                          "daypart": {
                            "type": "string"
                          },
                          "device_profile": {
                            "type": "string"
                          },
                          "geo": {
                            "type": "string"
                          },
                          "model_type": {
                            "type": "string"
                          },
                          "sample_count": {
                            "type": "integer"
                          },
                          "updated_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "venue_subtype": {
                            "type": "string"
                          },
                          "venue_type": {
                            "type": "string"
                          },
                          "version": {
                            "type": "string"
                          },
                          "weights": {
                            "type": "object"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "FEIN model status"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get FEIN model status",
        "tags": [
          "FEIN Analytics"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/readiness": {
      "get": {
        "operationId": "getFeinReadiness",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gates": {
                      "properties": {
                        "backfill_reliability": {
                          "type": "boolean"
                        },
                        "daypart_completeness": {
                          "type": "boolean"
                        },
                        "duplicate_rate": {
                          "type": "boolean"
                        },
                        "ecosystem_activation": {
                          "type": "boolean"
                        },
                        "schema_converged": {
                          "type": "boolean"
                        },
                        "venue_type_completeness": {
                          "type": "boolean"
                        }
                      },
                      "type": "object"
                    },
                    "generated_at": {
                      "format": "date-time",
                      "type": "string"
                    },
                    "quality": {
                      "properties": {
                        "active_fein_api_keys": {
                          "type": "integer"
                        },
                        "active_fein_webhooks": {
                          "type": "integer"
                        },
                        "daypart_null_pct": {
                          "nullable": true,
                          "type": "number"
                        },
                        "device_rows_24h": {
                          "type": "integer"
                        },
                        "duplicate_rate_pct": {
                          "nullable": true,
                          "type": "number"
                        },
                        "venue_unknown_or_null_pct": {
                          "nullable": true,
                          "type": "number"
                        }
                      },
                      "type": "object"
                    },
                    "schema_state": {
                      "properties": {
                        "complete": {
                          "type": "boolean"
                        },
                        "errors": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "status": {
                      "enum": [
                        "green",
                        "yellow"
                      ],
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "FEIN readiness report"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get FEIN readiness report",
        "tags": [
          "FEIN Analytics"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/screen-intelligence/{screenId}": {
      "get": {
        "operationId": "getScreenIntelligence",
        "parameters": [
          {
            "$ref": "#/components/parameters/ScreenIdPath"
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "audience_metrics": {
                      "items": {
                        "properties": {
                          "ad_receptivity_score": {
                            "type": "number"
                          },
                          "avg_attention": {
                            "type": "number"
                          },
                          "avg_dwell_time_ms": {
                            "type": "integer"
                          },
                          "avg_face_count": {
                            "type": "number"
                          },
                          "emotional_engagement_score": {
                            "type": "number"
                          },
                          "estimated_occupancy": {
                            "type": "number"
                          },
                          "inferred_venue_type": {
                            "type": "string"
                          },
                          "window_end": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "cpm_history": {
                      "items": {
                        "properties": {
                          "cpm_rate": {
                            "type": "number"
                          },
                          "impressions": {
                            "type": "integer"
                          },
                          "period_date": {
                            "format": "date",
                            "type": "string"
                          },
                          "revenue_cents": {
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "inference": {
                      "items": {
                        "properties": {
                          "attention_score": {
                            "type": "number"
                          },
                          "bid_outcome": {
                            "type": "string"
                          },
                          "body_engagement": {
                            "type": "number"
                          },
                          "clearing_cpm": {
                            "type": "number"
                          },
                          "creative_id": {
                            "type": "string"
                          },
                          "daypart": {
                            "type": "string"
                          },
                          "emotion_score": {
                            "type": "number"
                          },
                          "face_count": {
                            "type": "integer"
                          },
                          "inference_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "inference_snapshot_id": {
                            "format": "uuid",
                            "type": "string"
                          },
                          "inference_source": {
                            "type": "string"
                          },
                          "venue_type": {
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "screen_id": {
                      "type": "string"
                    },
                    "venue_intelligence": {
                      "items": {
                        "properties": {
                          "avg_attention": {
                            "type": "number"
                          },
                          "avg_dwell_ms": {
                            "type": "integer"
                          },
                          "avg_engagement_score": {
                            "type": "number"
                          },
                          "avg_receptivity_score": {
                            "type": "number"
                          },
                          "dominant_ambience": {
                            "type": "string"
                          },
                          "dominant_mood": {
                            "type": "string"
                          },
                          "dominant_occupancy": {
                            "type": "string"
                          },
                          "unique_visitors": {
                            "type": "integer"
                          },
                          "window_end": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "window_start": {
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Screen intelligence data"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Get screen intelligence",
        "tags": [
          "FEIN Analytics"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/stream": {
      "get": {
        "operationId": "streamFeinEvents",
        "parameters": [
          {
            "description": "Optional comma-separated event filter",
            "in": "query",
            "name": "events",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "SSE event stream"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Stream FEIN events over SSE",
        "tags": [
          "FEIN Webhooks"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/webhooks": {
      "get": {
        "operationId": "listFeinWebhooks",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "webhooks": [
                    {
                      "callback_url": "https://example.com/hooks/trillboards-fein",
                      "created_at": "2026-08-12T11:20:03.771Z",
                      "events": [
                        "inference.completed"
                      ],
                      "failure_count": 0,
                      "id": "68b1c2d3e4f5a60718293a4b",
                      "is_active": true,
                      "last_delivered_at": "2026-08-31T08:59:12.004Z"
                    }
                  ]
                },
                "schema": {
                  "properties": {
                    "webhooks": {
                      "items": {
                        "properties": {
                          "callback_url": {
                            "format": "uri",
                            "type": "string"
                          },
                          "created_at": {
                            "format": "date-time",
                            "type": "string"
                          },
                          "events": {
                            "description": "Event names this subscription receives.",
                            "items": {
                              "type": "string"
                            },
                            "type": "array"
                          },
                          "failure_count": {
                            "description": "Cumulative delivery failures.",
                            "type": "integer"
                          },
                          "id": {
                            "type": "string"
                          },
                          "is_active": {
                            "type": "boolean"
                          },
                          "last_delivered_at": {
                            "description": "`null` if nothing has ever been delivered.",
                            "format": "date-time",
                            "nullable": true,
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Every webhook registered against the calling API key, newest first.\nScoped to that key — you cannot see another key's subscriptions.\n\n`failure_count` is cumulative delivery failures. A subscription is\nnot deleted when it fails; watch `is_active` together with\n`last_delivered_at` to spot one that has gone quiet.\n"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "List FEIN webhooks",
        "tags": [
          "FEIN Webhooks"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      },
      "post": {
        "operationId": "registerFeinWebhook",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "events": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "secret": {
                    "type": "string"
                  },
                  "url": {
                    "format": "uri",
                    "type": "string"
                  }
                },
                "required": [
                  "url",
                  "events"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "FEIN webhook registered"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Register FEIN webhook",
        "tags": [
          "FEIN Webhooks"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/fein/webhooks/{id}": {
      "delete": {
        "operationId": "deleteFeinWebhook",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "deleted": true
                },
                "schema": {
                  "properties": {
                    "deleted": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Subscription deleted. The body is exactly `{\"deleted\": true}` — the\nid is not echoed back.\n\nThe delete is scoped to the calling API key, so deleting an id that\nexists but belongs to another key is a `404`, not a `403`. A `404`\ntherefore means \"not yours or not there\" and does not confirm the\nid is unused.\n"
          },
          "401": {
            "$ref": "#/components/responses/ApiKeyUnauthorized"
          }
        },
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "summary": "Delete FEIN webhook",
        "tags": [
          "FEIN Webhooks"
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/forecast": {
      "post": {
        "description": "Given targeting criteria and a budget, estimates how many impressions you can\nexpect, your likely win rate, and average CPM. Forecasts are computed from\n7 days of historical ClickHouse rollup data from matching screens.\n\n**Use this before creating a campaign** to understand expected delivery and\noptimize your budget allocation.\n\nThe `confidence` field indicates forecast reliability:\n- **high** — 50%+ of matching screens have 7+ days of historical data\n- **medium** — Some screens have data but coverage is incomplete\n- **low** — Insufficient historical data; estimates are rough projections\n",
        "operationId": "forecast",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "budget_usd": 5000,
                "end_date": "2026-04-17",
                "start_date": "2026-04-10",
                "targeting": {
                  "country": "US",
                  "venue_type": "retail"
                }
              },
              "schema": {
                "properties": {
                  "budget_usd": {
                    "description": "Total campaign budget in USD. Must be greater than 0. The forecast\nwill estimate how many impressions this budget can buy at current\nmarket clearing prices.\n",
                    "example": 5000,
                    "minimum": 0.01,
                    "type": "number"
                  },
                  "end_date": {
                    "description": "Campaign end date in YYYY-MM-DD format. If omitted, defaults\nto 7 days after start_date.\n",
                    "example": "2026-04-17",
                    "format": "date",
                    "type": "string"
                  },
                  "pricing_model": {
                    "default": "cpm",
                    "description": "Pricing model to use for the forecast.\n- **cpm** — Standard cost per mille (thousand impressions)\n- **vas_cpm** — Verified Attention Second CPM (premium pricing based on attention)\n",
                    "enum": [
                      "cpm",
                      "vas_cpm"
                    ],
                    "example": "cpm",
                    "type": "string"
                  },
                  "start_date": {
                    "description": "Campaign start date in YYYY-MM-DD format. If omitted, defaults\nto tomorrow.\n",
                    "example": "2026-04-10",
                    "format": "date",
                    "type": "string"
                  },
                  "targeting": {
                    "description": "Targeting criteria to scope the forecast. If omitted, forecast\ncovers all available inventory.\n",
                    "properties": {
                      "city": {
                        "description": "City name",
                        "example": "Los Angeles",
                        "type": "string"
                      },
                      "country": {
                        "description": "ISO 3166-1 alpha-2 country code",
                        "example": "US",
                        "type": "string"
                      },
                      "state": {
                        "description": "State or province code",
                        "example": "CA",
                        "type": "string"
                      },
                      "venue_type": {
                        "description": "Venue type filter",
                        "example": "retail",
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "budget_usd"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "avg_cpm": 7,
                  "campaign_days": 7,
                  "confidence": "high",
                  "daily_impressions": 102040,
                  "estimated_impressions": 714285,
                  "estimated_spend_usd": 4999.99,
                  "estimated_win_rate": 0.68,
                  "matching_screen_count": 342,
                  "pricing_model": "cpm"
                },
                "schema": {
                  "properties": {
                    "avg_cpm": {
                      "description": "Average CPM in USD across matching screens based on 7-day\nhistorical clearing prices.\n",
                      "example": 7,
                      "type": "number"
                    },
                    "campaign_days": {
                      "description": "Number of days in the campaign period",
                      "example": 7,
                      "type": "integer"
                    },
                    "confidence": {
                      "description": "Forecast confidence level based on historical data availability.\n- high: 50%+ screens have 7+ days of data\n- medium: some screens have data but coverage is incomplete\n- low: insufficient data, rough projections only\n",
                      "enum": [
                        "high",
                        "medium",
                        "low"
                      ],
                      "example": "high",
                      "type": "string"
                    },
                    "daily_impressions": {
                      "description": "Estimated impressions per day (total divided by campaign days)",
                      "example": 102040,
                      "type": "integer"
                    },
                    "estimated_impressions": {
                      "description": "Estimated total impressions the budget can buy over the campaign period\nat current market clearing prices.\n",
                      "example": 714285,
                      "type": "integer"
                    },
                    "estimated_spend_usd": {
                      "description": "Estimated total spend in USD. May be less than budget_usd if inventory\nis limited for the targeting criteria.\n",
                      "example": 4999.99,
                      "type": "number"
                    },
                    "estimated_win_rate": {
                      "description": "Estimated auction win rate as a decimal (0.0 to 1.0). Based on\nhistorical win rates for similar targeting and CPM ranges.\n",
                      "example": 0.68,
                      "maximum": 1,
                      "minimum": 0,
                      "type": "number"
                    },
                    "matching_screen_count": {
                      "description": "Number of active screens matching the targeting criteria",
                      "example": 342,
                      "type": "integer"
                    },
                    "message": {
                      "description": "Present only when zero screens match the targeting criteria. When this field is present, all other fields are omitted.\n",
                      "example": "No screens match the specified targeting criteria",
                      "nullable": true,
                      "type": "string"
                    },
                    "pricing_model": {
                      "description": "The pricing model used for this forecast",
                      "enum": [
                        "cpm",
                        "vas_cpm"
                      ],
                      "example": "cpm",
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Delivery forecast computed successfully. When no screens match the\ntargeting criteria, the response contains only a `message` field\n(e.g., `{ \"message\": \"No screens match the specified targeting criteria\" }`)\ninstead of the full forecast object.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "budget_usd is required and must be greater than 0",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request parameters. The `budget_usd` field is required and must be\ngreater than 0. Date formats must be valid YYYY-MM-DD.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Estimate campaign delivery and spend",
        "tags": [
          "Inventory"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/forecast\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"targeting\":{\"country\":\"US\",\"venue_type\":\"retail\"},\"budget_usd\":5000,\"start_date\":\"2026-04-10\",\"end_date\":\"2026-04-17\"}'\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/market/cpm-benchmarks": {
      "get": {
        "description": "Returns p25/median/p75/avg CPM percentiles per venue type, optionally\nfiltered by country, state, or daypart. Also returns a market overview with\ntotal SSP-enabled screens, average fill rate, and average completion rate.\n\n**Data source:** `bid_intelligence` and `openrtb_vast_screen_hourly` PG rollup\ntables. Cached in memory for 10 minutes per query.\n\n**Rate limit category:** Reporting (100 req/min).\n",
        "operationId": "getCpmBenchmarks",
        "parameters": [
          {
            "description": "Filter to a single venue category (e.g., `coffee_shop`, `gym`).",
            "example": "coffee_shop",
            "in": "query",
            "name": "venue_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "ISO 3166-1 alpha-2 country code.",
            "example": "US",
            "in": "query",
            "name": "country",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "State or province filter.",
            "example": "NY",
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Row grouping:\n- `none` — single row per venue type (default)\n- `daypart` — one row per venue type × daypart\n- `daily` — one row per venue type × day\n",
            "example": "daypart",
            "in": "query",
            "name": "granularity",
            "required": false,
            "schema": {
              "default": "none",
              "enum": [
                "none",
                "daypart",
                "daily"
              ],
              "type": "string"
            }
          },
          {
            "description": "Lookback window in days (default 30, maximum 365).",
            "example": 30,
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "default": 30,
              "maximum": 365,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "benchmarks": [
                      {
                        "country": "US",
                        "cpm": {
                          "avg": 7.42,
                          "median": 7.25,
                          "p25": 4.5,
                          "p75": 10.8
                        },
                        "period": "30d",
                        "sample_size": 14520,
                        "venue_type": "coffee_shop"
                      },
                      {
                        "country": "US",
                        "cpm": {
                          "avg": 6.08,
                          "median": 5.9,
                          "p25": 3.8,
                          "p75": 8.2
                        },
                        "period": "30d",
                        "sample_size": 8340,
                        "venue_type": "gym"
                      }
                    ],
                    "market_overview": {
                      "avg_completion_rate": 0.94,
                      "avg_fill_rate": 0.87,
                      "total_screens_ssp": 8420
                    }
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "benchmarks": {
                          "items": {
                            "$ref": "#/components/schemas/CpmBenchmark"
                          },
                          "type": "array"
                        },
                        "market_overview": {
                          "properties": {
                            "avg_completion_rate": {
                              "example": 0.94,
                              "maximum": 1,
                              "minimum": 0,
                              "type": "number"
                            },
                            "avg_fill_rate": {
                              "example": 0.87,
                              "maximum": 1,
                              "minimum": 0,
                              "type": "number"
                            },
                            "total_screens_ssp": {
                              "example": 8420,
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "CPM benchmarks and market overview.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "CPM percentile benchmarks by venue and geography",
        "tags": [
          "Market Intelligence"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/market/cpm-benchmarks\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/market/demand-analysis": {
      "get": {
        "description": "Returns demand-side analytics: top advertiser categories by bid volume, daypart\ndemand curves (avg CPM + fill rate by morning/afternoon/evening/late_night),\nand segment CPM premium lift (if the segment correlation table is populated).\n\n**Data source:** `bid_intelligence` and `openrtb_vast_screen_hourly` PG rollup\ntables. Segment data is sourced from `segment_cpm_correlation` when available\n(graceful degradation returns an empty `segment_premiums` array otherwise).\n\n**Rate limit category:** Reporting (100 req/min).\n",
        "operationId": "getDemandAnalysis",
        "parameters": [
          {
            "description": "Lookback window in days (default 30, maximum 365).",
            "example": 30,
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "default": 30,
              "maximum": 365,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Max top categories to return (default 10, maximum 100).",
            "example": 10,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 10,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "daypart_curves": [
                      {
                        "avg_cpm": 5.84,
                        "bid_volume": 14250,
                        "daypart": "morning",
                        "fill_rate": 0.82
                      },
                      {
                        "avg_cpm": 7.12,
                        "bid_volume": 19840,
                        "daypart": "afternoon",
                        "fill_rate": 0.88
                      },
                      {
                        "avg_cpm": 9.28,
                        "bid_volume": 22040,
                        "daypart": "evening",
                        "fill_rate": 0.91
                      },
                      {
                        "avg_cpm": 4.1,
                        "bid_volume": 3820,
                        "daypart": "late_night",
                        "fill_rate": 0.64
                      }
                    ],
                    "segment_premiums": [
                      {
                        "cpm_lift_pct": 38.4,
                        "sample_size": 1240,
                        "segment": "high_attention"
                      },
                      {
                        "cpm_lift_pct": 22.1,
                        "sample_size": 840,
                        "segment": "live_faces_gt_5"
                      }
                    ],
                    "top_categories": [
                      {
                        "avg_cpm": 8.32,
                        "bid_count": 28450,
                        "category": "IAB19_Technology_Computing"
                      },
                      {
                        "avg_cpm": 6.12,
                        "bid_count": 21080,
                        "category": "IAB8_Food_Drink"
                      },
                      {
                        "avg_cpm": 5.74,
                        "bid_count": 18420,
                        "category": "IAB7_Health_Fitness"
                      }
                    ]
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "daypart_curves": {
                          "items": {
                            "$ref": "#/components/schemas/DaypartCurve"
                          },
                          "type": "array"
                        },
                        "segment_premiums": {
                          "items": {
                            "properties": {
                              "cpm_lift_pct": {
                                "example": 38.4,
                                "type": "number"
                              },
                              "sample_size": {
                                "example": 1240,
                                "type": "integer"
                              },
                              "segment": {
                                "example": "high_attention",
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "top_categories": {
                          "items": {
                            "properties": {
                              "avg_cpm": {
                                "example": 8.32,
                                "type": "number"
                              },
                              "bid_count": {
                                "example": 28450,
                                "type": "integer"
                              },
                              "category": {
                                "example": "IAB19_Technology_Computing",
                                "type": "string"
                              }
                            },
                            "type": "object"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Demand analysis breakdown.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Top categories, daypart curves, segment premiums",
        "tags": [
          "Market Intelligence"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/market/demand-analysis\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/onboard": {
      "post": {
        "description": "Self-serve DSP registration. Recommended flow: self-mint one platform API\nkey in Earner Settings → Partner API, send it as `Authorization: Bearer <key>`, and this\nendpoint links that same key to the DSP seat and grants the DSP/MCP scopes.\nIt does not return or rotate the raw key in this flow.\n\nFor backwards compatibility only, a request with no API key still mints a\n`tb_dsp_*` key and shows it once. A supplied but invalid key is rejected; it\nnever falls back to minting a new credential.\n\n**Sandbox mode** provides full API access for integration testing. Bids are validated\nand scored but do not enter the live waterfall auction. Contact\ndevelopers@trillboards.com to upgrade to production.\n\nThe `seat_id` is your permanent OpenRTB buyer seat identifier. It must be unique\nacross all registered DSPs and cannot be changed after registration. Use a short,\nmemorable identifier like your company abbreviation.\n\n**Rate limit:** 5 requests per hour (applies to both successful and failed attempts).\n",
        "operationId": "onboard",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "billing_email": "billing@acme-ads.com",
                "company_name": "Acme Advertising",
                "contact_email": "buyer@acme-ads.com",
                "notification_url": "https://acme-ads.com/webhooks/trillboards/notifications",
                "openrtb_version": "2.6",
                "seat_id": "acme_ads",
                "webhook_url": "https://acme-ads.com/webhooks/trillboards/events"
              },
              "schema": {
                "properties": {
                  "billing_email": {
                    "description": "Billing contact email for invoices and payment notifications.\nIf not provided, billing communications go to the contact_email.\n",
                    "example": "billing@acme-ads.com",
                    "format": "email",
                    "type": "string"
                  },
                  "company_name": {
                    "description": "Your company or organization name. This appears in auction logs\nand reporting dashboards. Use your legal entity name.\n",
                    "example": "Acme Advertising",
                    "maxLength": 200,
                    "minLength": 2,
                    "type": "string"
                  },
                  "contact_email": {
                    "description": "Primary technical contact email. Used for API key recovery,\nsandbox-to-production upgrade notifications, and integration\nsupport. Must be a valid email address.\n",
                    "example": "buyer@acme-ads.com",
                    "format": "email",
                    "type": "string"
                  },
                  "notification_url": {
                    "description": "HTTPS endpoint for win/loss notification webhooks. When your bid\nwins or loses an auction, a POST request is sent to this URL with\nthe bid ID, clearing price, and screen details. Must be HTTPS.\n",
                    "example": "https://acme-ads.com/webhooks/trillboards/notifications",
                    "format": "uri",
                    "type": "string"
                  },
                  "openrtb_version": {
                    "default": "2.6",
                    "description": "OpenRTB protocol version your DSP supports. Currently only\nversion 2.6 is supported. Defaults to \"2.6\" if not specified.\n",
                    "enum": [
                      "2.5",
                      "2.6"
                    ],
                    "example": "2.6",
                    "type": "string"
                  },
                  "seat_id": {
                    "description": "Unique OpenRTB buyer seat identifier. Must be alphanumeric with\nhyphens and underscores only, between 2 and 64 characters.\nThis becomes your permanent identifier in all bid requests and\nauction logs. Cannot be changed after registration.\n",
                    "example": "acme_ads",
                    "maxLength": 64,
                    "minLength": 2,
                    "pattern": "^[a-zA-Z0-9_-]{2,64}$",
                    "type": "string"
                  },
                  "webhook_url": {
                    "description": "HTTPS endpoint for general event webhooks (creative moderation\nresults, deal status changes, campaign updates). Must be HTTPS.\n",
                    "example": "https://acme-ads.com/webhooks/trillboards/events",
                    "format": "uri",
                    "type": "string"
                  }
                },
                "required": [
                  "company_name",
                  "seat_id",
                  "contact_email"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "api_key": {
                      "key": "tb_dsp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4",
                      "prefix": "tb_dsp_a1b2c",
                      "rate_limit_rpm": 100,
                      "reused_platform_key": false,
                      "scopes": [
                        "bid:write",
                        "inventory:read",
                        "deals:read",
                        "stats:read",
                        "campaigns:write",
                        "reports:read",
                        "proof:read",
                        "mcp:tools"
                      ]
                    },
                    "registration": {
                      "company_name": "Acme Advertising",
                      "created_at": "2026-04-03T14:22:31.000Z",
                      "id": "a3f8c9e1-7b42-4d6a-9e3f-1c8b5d2a7f04",
                      "openrtb_version": "2.6",
                      "seat_id": "acme_ads",
                      "status": "sandbox"
                    },
                    "sandbox": {
                      "auth": "Include Authorization: Bearer <platform_api_key> on control-plane requests",
                      "description": "Your DSP is in sandbox mode. Bids are validated and scored but do not enter the live auction. Contact developers@trillboards.com to upgrade to production.",
                      "endpoints": {
                        "adslots": "GET /openrtb/v2/adslots",
                        "bid": "POST /openrtb/v2/bid",
                        "stats": "GET /openrtb/v2/stats"
                      },
                      "rate_limit": "100 requests/min (upgraded to 1000 on approval)"
                    }
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "description": "Registration result containing credentials and sandbox instructions",
                      "properties": {
                        "api_key": {
                          "description": "Credential linkage. `reused_platform_key=true` means the caller's\nexisting key was linked and no raw key is returned. The `key` field\nappears only in the backwards-compatible anonymous onboarding flow.\n",
                          "properties": {
                            "key": {
                              "description": "Legacy flow only. Full API key; prefer `Authorization: Bearer <key>`.\nThis value is never returned again by any endpoint. Store it in a\nsecrets manager immediately.\n",
                              "example": "tb_dsp_a1b2c3d4e5f6g7h8i9j0k1l2m3n4",
                              "type": "string"
                            },
                            "key_id": {
                              "description": "Existing centralized key ID when a platform key was reused.",
                              "format": "uuid",
                              "type": "string"
                            },
                            "prefix": {
                              "description": "Key prefix for identification. This is the only portion visible\nin logs and admin interfaces after initial display.\n",
                              "example": "tb_dsp_a1b2c",
                              "type": "string"
                            },
                            "rate_limit_rpm": {
                              "description": "Maximum requests per minute for this API key. Sandbox keys\nare limited to 100 RPM. Production keys get 1,000 RPM.\n",
                              "example": 100,
                              "type": "integer"
                            },
                            "reused_platform_key": {
                              "description": "True when onboarding linked the supplied key instead of minting another.",
                              "type": "boolean"
                            },
                            "scopes": {
                              "description": "Permissions granted to this API key. Sandbox keys receive all\nscopes but rate limits are lower.\n",
                              "example": [
                                "bid:write",
                                "inventory:read",
                                "deals:read",
                                "stats:read",
                                "campaigns:write",
                                "reports:read",
                                "proof:read",
                                "mcp:tools"
                              ],
                              "items": {
                                "enum": [
                                  "bid:write",
                                  "inventory:read",
                                  "deals:read",
                                  "stats:read",
                                  "campaigns:write",
                                  "reports:read",
                                  "proof:read",
                                  "mcp:tools"
                                ],
                                "type": "string"
                              },
                              "type": "array"
                            }
                          },
                          "type": "object"
                        },
                        "registration": {
                          "description": "Your DSP registration record",
                          "properties": {
                            "company_name": {
                              "description": "Registered company name",
                              "example": "Acme Advertising",
                              "type": "string"
                            },
                            "created_at": {
                              "description": "Registration timestamp in ISO 8601 format",
                              "example": "2026-04-03T14:22:31.000Z",
                              "format": "date-time",
                              "type": "string"
                            },
                            "id": {
                              "description": "Unique registration ID for support reference",
                              "example": "a3f8c9e1-7b42-4d6a-9e3f-1c8b5d2a7f04",
                              "format": "uuid",
                              "type": "string"
                            },
                            "openrtb_version": {
                              "description": "OpenRTB protocol version configured for this DSP",
                              "example": "2.6",
                              "type": "string"
                            },
                            "seat_id": {
                              "description": "Your permanent OpenRTB buyer seat identifier",
                              "example": "acme_ads",
                              "type": "string"
                            },
                            "status": {
                              "description": "Current DSP status. All new registrations start as \"sandbox\".\nContact support to upgrade to \"approved\" for production access.\n",
                              "enum": [
                                "sandbox",
                                "approved",
                                "suspended",
                                "revoked"
                              ],
                              "example": "sandbox",
                              "type": "string"
                            }
                          },
                          "type": "object"
                        },
                        "sandbox": {
                          "description": "Sandbox environment details and getting-started instructions",
                          "properties": {
                            "auth": {
                              "description": "Authentication instructions",
                              "example": "Include Authorization: Bearer <platform_api_key> on control-plane requests",
                              "type": "string"
                            },
                            "description": {
                              "description": "Human-readable explanation of sandbox mode",
                              "example": "Your DSP is in sandbox mode. Bids are validated and scored but do not enter the live auction. Contact developers@trillboards.com to upgrade to production.",
                              "type": "string"
                            },
                            "endpoints": {
                              "description": "Key endpoints to start integrating with",
                              "properties": {
                                "adslots": {
                                  "description": "Inventory discovery endpoint",
                                  "example": "GET /openrtb/v2/adslots",
                                  "type": "string"
                                },
                                "bid": {
                                  "description": "Bid submission endpoint",
                                  "example": "POST /openrtb/v2/bid",
                                  "type": "string"
                                },
                                "stats": {
                                  "description": "Performance metrics endpoint",
                                  "example": "GET /openrtb/v2/stats",
                                  "type": "string"
                                }
                              },
                              "type": "object"
                            },
                            "rate_limit": {
                              "description": "Rate limit summary for sandbox mode",
                              "example": "100 requests/min (upgraded to 1000 on approval)",
                              "type": "string"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "description": "Whether the registration was successful",
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "DSP registered successfully. When a platform key was supplied, the response\nidentifies it as reused and never echoes the raw secret. Legacy unauthenticated\nonboarding returns a newly minted key once.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 5,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 4,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 3540,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "company_name, seat_id, and contact_email are required",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "error": {
                      "description": "Human-readable description of what went wrong",
                      "example": "company_name, seat_id, and contact_email are required",
                      "type": "string"
                    },
                    "success": {
                      "description": "Always false for error responses",
                      "example": false,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Invalid request parameters. One or more required fields are missing or malformed.\nCheck the error message for specific field validation failures.\n"
          },
          "409": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Seat ID \"acme_ads\" is already registered (status: sandbox)",
                  "success": false
                },
                "schema": {
                  "properties": {
                    "error": {
                      "description": "Conflict description including the existing registration status",
                      "example": "Seat ID \"acme_ads\" is already registered (status: sandbox)",
                      "type": "string"
                    },
                    "success": {
                      "description": "Always false for error responses",
                      "example": false,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Seat ID conflict. The requested `seat_id` is already registered by another DSP.\nChoose a different seat ID. Seat IDs from suspended or revoked DSPs are also\nunavailable.\n"
          },
          "415": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Content-Type must be application/json",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Unsupported media type. The request Content-Type header must be `application/json`.\n"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Rate limit exceeded. Maximum 5 onboarding requests per hour.",
                  "retry_after": 2847,
                  "success": false
                },
                "schema": {
                  "properties": {
                    "error": {
                      "example": "Rate limit exceeded. Maximum 5 onboarding requests per hour.",
                      "type": "string"
                    },
                    "retry_after": {
                      "description": "Seconds to wait before retrying",
                      "example": 2847,
                      "type": "integer"
                    },
                    "success": {
                      "example": false,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Rate limit exceeded. Onboarding is limited to 5 requests per hour per IP address.\nWait for the period indicated by the `Retry-After` header.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 5,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining (always 0 when rate limited)",
                "schema": {
                  "example": 0,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 2847,
                  "type": "integer"
                }
              },
              "Retry-After": {
                "description": "Seconds to wait before retrying",
                "schema": {
                  "example": 2847,
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {},
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Register a new DSP",
        "tags": [
          "Onboarding"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/onboard\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"company_name\":\"Acme Advertising\",\"seat_id\":\"acme_ads\",\"contact_email\":\"buyer@acme-ads.com\",\"billing_email\":\"billing@acme-ads.com\",\"openrtb_version\":\"2.6\",\"notification_url\":\"https://acme-ads.com/webhooks/trillboards/notifications\",\"webhook_url\":\"https://acme-ads.com/webhooks/trillboards/events\"}'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/openapi.json": {
      "get": {
        "description": "Returns the OpenAPI 3 JSON document describing the `/openrtb/v2/*`\nendpoints (FEIN, audience reach, semantic search). Designed for\nagentic clients that build typed SDKs on demand. The v1 surface\nships its spec under `/docs/openapi/programmatic-api.yaml` (this\nfile).\n",
        "operationId": "getOpenRtbV2OpenApiSpec",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            },
            "description": "OpenAPI 3 spec for OpenRTB v2"
          }
        },
        "security": [],
        "summary": "Machine-readable OpenAPI 3 spec for the OpenRTB v2 surface",
        "tags": [
          "Internal"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "programmatic"
      }
    },
    "/openrtb/v2/proof-of-play": {
      "get": {
        "description": "Returns verified play records from `completed_impressions` so DSP buyers can\nreconcile billing against actual screen deliveries. Each record is tagged with\na `verification_tier`:\n\n- **`signed`** — Ed25519 signed proof-of-play from the screen (highest trust)\n- **`event_confirmed`** — VAST `complete` event fired by the IMA SDK\n- **`partner_reported`** — Partner SDK lifecycle completion report\n\nImpression callbacks are pixel fires, not completed plays, and are not\nreturned by this DSP-facing proof surface.\n\n**Required:** `start_date` and `end_date`. These are always included in the\nquery WHERE clause for partition pruning — the `completed_impressions` table\nis partitioned monthly, so omitting them would trigger a full scan and time out.\n\n**Rate limit category:** Reporting (100 req/min).\n",
        "operationId": "getProofOfPlay",
        "parameters": [
          {
            "description": "ISO 8601 timestamp — lower bound on `completed_at` (inclusive).",
            "example": "2026-05-01T00:00:00Z",
            "in": "query",
            "name": "start_date",
            "required": true,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "ISO 8601 timestamp — upper bound on `completed_at` (exclusive).",
            "example": "2026-05-14T23:59:59Z",
            "in": "query",
            "name": "end_date",
            "required": true,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Filter to a single screen (mongo_earnerscreen_id).",
            "example": "65a1b2c3d4e5f6a7b8c9d0e1",
            "in": "query",
            "name": "screen_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter to a single creative.",
            "example": "creative_summer_sale",
            "in": "query",
            "name": "creative_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter to a single campaign (maps to `ad_id`).",
            "example": "cmp_acme_q2_2026",
            "in": "query",
            "name": "campaign_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Restrict to a single verification tier.\n",
            "example": "signed",
            "in": "query",
            "name": "verification_tier",
            "required": false,
            "schema": {
              "enum": [
                "signed",
                "event_confirmed",
                "partner_reported"
              ],
              "type": "string"
            }
          },
          {
            "description": "Rows to return (default 50, maximum 200).",
            "example": 50,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Pagination offset (default 0).",
            "example": 0,
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "pagination": {
                      "limit": 50,
                      "offset": 0,
                      "total": 1420
                    },
                    "proofs": [
                      {
                        "creative_id": "creative_summer_sale",
                        "display_area_ratio": 1,
                        "display_mode": "fullscreen",
                        "duration_seconds": 15,
                        "givt_passed": true,
                        "play_id": "play_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
                        "played_at": "2026-05-01T14:22:31.000Z",
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e1",
                        "signature": "ed25519:AQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyA=",
                        "verification_tier": "signed"
                      },
                      {
                        "creative_id": "creative_summer_sale",
                        "display_area_ratio": 1,
                        "display_mode": "fullscreen",
                        "duration_seconds": 15,
                        "givt_passed": true,
                        "play_id": "play_01HS8A3V1Y8W5R0Q3NXLKM6OD",
                        "played_at": "2026-05-01T14:22:45.000Z",
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e2",
                        "signature": null,
                        "verification_tier": "event_confirmed"
                      }
                    ],
                    "summary": {
                      "givt_pass_rate": 0.9943,
                      "total_plays": 1420,
                      "verified_event": 142,
                      "verified_partner_reported": 1266,
                      "verified_signed": 12
                    }
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "pagination": {
                          "properties": {
                            "limit": {
                              "example": 50,
                              "type": "integer"
                            },
                            "offset": {
                              "example": 0,
                              "type": "integer"
                            },
                            "total": {
                              "example": 1420,
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        },
                        "proofs": {
                          "items": {
                            "$ref": "#/components/schemas/ProofOfPlayRecord"
                          },
                          "type": "array"
                        },
                        "summary": {
                          "properties": {
                            "givt_pass_rate": {
                              "description": "Fraction of plays that passed GIVT checks.",
                              "example": 0.9943,
                              "maximum": 1,
                              "minimum": 0,
                              "type": "number"
                            },
                            "total_plays": {
                              "description": "Total play records matching the filters.",
                              "example": 1420,
                              "type": "integer"
                            },
                            "verified_event": {
                              "description": "Plays confirmed by VAST `complete` event.",
                              "example": 142,
                              "type": "integer"
                            },
                            "verified_partner_reported": {
                              "description": "Plays confirmed by partner SDK lifecycle report.",
                              "example": 1266,
                              "type": "integer"
                            },
                            "verified_signed": {
                              "description": "Plays with Ed25519 signature (`proof_of_play`).",
                              "example": 12,
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Proof records with tier-summary statistics.",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "schema": {
                  "example": 55,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "start_date and end_date query parameters are required",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request. Common causes: missing `start_date`/`end_date`, invalid\nISO date strings, `start_date` after `end_date`, or invalid\n`verification_tier` value.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Verified ad play records with tier breakdown",
        "tags": [
          "Proof of Play"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/proof-of-play\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/reports/creatives": {
      "get": {
        "description": "Returns a per-creative aggregate of delivery for the authenticated\nDSP seat over the requested lookback window. Sources directly from\nClickHouse (`mv_creative_screen_daily`); when CH is unavailable the\nendpoint returns `data: []` (200), not a 503 — same posture as the\nsibling earner content-performance endpoint. The legacy PostgreSQL\nmirror `openrtb_creative_screen_daily` was retired in CUT-11\n(2026-05). Results are sorted by `plays` descending.\n\nThe `source_provider` filter is applied implicitly: each DSP seat\nsees rows where `source_provider = inbound_dsp_<seat>` — the\ncanonical mapping established by the inbound bid cache and the\nwaterfall mediator.\n",
        "operationId": "getReportCreatives",
        "parameters": [
          {
            "description": "Number of days to look back (default 7, maximum 90)",
            "example": 7,
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "default": 7,
              "maximum": 90,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Maximum number of creatives to return (default 50, maximum 200)",
            "example": 50,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "count": 2,
                  "data": [
                    {
                      "ad_id": "ad_7b9c0d1e2f3a4b5c",
                      "creative_id": "crv_4f8a9c1b0d2e3f4a",
                      "play_seconds": 18705.45,
                      "plays": 1247,
                      "source_provider": "inbound_dsp_acme_ads",
                      "unique_screens": 38
                    },
                    {
                      "ad_id": "ad_0e1f2a3b4c5d6e7f",
                      "creative_id": "crv_1a2b3c4d5e6f7a8b",
                      "play_seconds": 6180,
                      "plays": 412,
                      "source_provider": "inbound_dsp_acme_ads",
                      "unique_screens": 17
                    }
                  ],
                  "period_days": 7,
                  "seat": "acme_ads",
                  "success": true
                },
                "schema": {
                  "properties": {
                    "count": {
                      "description": "Always `data.length` — the handler computes it from the rows it is about to send, so it can never disagree with the array.",
                      "example": 2,
                      "type": "integer"
                    },
                    "data": {
                      "description": "Per-creative rollup, sorted by `plays` descending.",
                      "items": {
                        "properties": {
                          "ad_id": {
                            "example": "ad_7b9c0d1e2f3a4b5c",
                            "type": "string"
                          },
                          "creative_id": {
                            "example": "crv_4f8a9c1b0d2e3f4a",
                            "type": "string"
                          },
                          "play_seconds": {
                            "example": 18705.45,
                            "type": "number"
                          },
                          "plays": {
                            "example": 1247,
                            "type": "integer"
                          },
                          "source_provider": {
                            "example": "inbound_dsp_acme_ads",
                            "nullable": true,
                            "type": "string"
                          },
                          "unique_screens": {
                            "example": 38,
                            "type": "integer"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "period_days": {
                      "example": 7,
                      "type": "integer"
                    },
                    "seat": {
                      "description": "DSP seat the rollup is scoped to",
                      "example": "acme_ads",
                      "nullable": true,
                      "type": "string"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Per-creative aggregate for the authenticated DSP seat",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Per-creative rollup for the authenticated DSP seat",
        "tags": [
          "Reporting"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/reports/creatives\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/reports/screens": {
      "get": {
        "description": "Returns canonical billable completed impressions broken down by screen\nfor the authenticated DSP seat. Useful for identifying delivered screens\nand verifying campaign execution.\n\n**Data source:** ClickHouse `mv_completed_impressions_hourly`, implicitly\nfiltered to `source_provider = inbound_dsp_<seat>`. Results are sorted by\nimpressions descending. A ClickHouse outage returns `data: []` with `200`;\nno global PostgreSQL fallback is used.\n",
        "operationId": "getReportScreens",
        "parameters": [
          {
            "description": "Number of days to look back (default 7, maximum 90)",
            "example": 7,
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "default": 7,
              "maximum": 90,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Maximum number of screens to return (default 50, maximum 200)",
            "example": 50,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "count": 2,
                  "data": [
                    {
                      "avg_cpm": 8.12,
                      "fills": 1247,
                      "hours_active": 98.5,
                      "impressions": 1247,
                      "screen_id": "scr_7f8a9b0c1d2e3f4a"
                    },
                    {
                      "avg_cpm": 7.85,
                      "fills": 983,
                      "hours_active": 112,
                      "impressions": 983,
                      "screen_id": "scr_2a3b4c5d6e7f8a9b"
                    },
                    {
                      "avg_cpm": 6.94,
                      "fills": 876,
                      "hours_active": 105.3,
                      "impressions": 876,
                      "screen_id": "scr_1d2e3f4a5b6c7d8e"
                    }
                  ],
                  "period_days": 7,
                  "success": true
                },
                "schema": {
                  "properties": {
                    "count": {
                      "description": "Number of screen rows returned",
                      "example": 2,
                      "type": "integer"
                    },
                    "data": {
                      "description": "Array of per-screen performance records, sorted by impressions\ndescending.\n",
                      "items": {
                        "properties": {
                          "avg_cpm": {
                            "description": "Average CPM on this screen",
                            "example": 8.12,
                            "type": "number"
                          },
                          "fills": {
                            "description": "Number of filled ad requests",
                            "example": 1247,
                            "type": "integer"
                          },
                          "hours_active": {
                            "description": "Total hours the screen was active and serving ads",
                            "example": 98.5,
                            "type": "number"
                          },
                          "impressions": {
                            "description": "Total impressions on this screen",
                            "example": 1247,
                            "type": "integer"
                          },
                          "screen_id": {
                            "description": "Screen identifier",
                            "example": "scr_7f8a9b0c1d2e3f4a",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "period_days": {
                      "description": "Number of days covered",
                      "example": 7,
                      "type": "integer"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Per-screen performance data",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Per-screen performance breakdown",
        "tags": [
          "Reporting"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/reports/screens\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/reports/summary": {
      "get": {
        "description": "Returns billable completed-impression totals for the authenticated DSP seat.\nSeat isolation is applied implicitly with the canonical\n`source_provider = inbound_dsp_<seat>` mapping.\n\n**Data source:** the ClickHouse `mv_completed_impressions_hourly` rollup,\nderived from canonical `completed_impressions`. Maximum lookback: 90 days.\nIf ClickHouse is unavailable, the endpoint returns a zero-valued `200`\nrather than falling back to a global, non-seat-scoped PostgreSQL rollup.\n\n`filled`, `no_fill`, and `fill_rate` are compatibility fields on this\ncompletion-ledger report: every returned impression is filled/completed,\n`no_fill` is `0`, and `fill_rate` is `1` when delivery exists (otherwise\n`0`). Use `GET /stats` for seat-level bid and win metrics.\n",
        "operationId": "getReportSummary",
        "parameters": [
          {
            "description": "Number of days to look back (default 7, maximum 90)",
            "example": 7,
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "default": 7,
              "maximum": 90,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "avg_cpm": 7.42,
                    "completed": 28430,
                    "fill_rate": 1,
                    "filled": 28430,
                    "no_fill": 0,
                    "period_days": 7,
                    "total_impressions": 28430,
                    "unique_screens": 87
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "avg_cpm": {
                          "description": "Revenue-weighted CPM across priced impressions in USD",
                          "example": 7.42,
                          "type": "number"
                        },
                        "completed": {
                          "description": "Canonical completed impressions",
                          "example": 28430,
                          "type": "integer"
                        },
                        "fill_rate": {
                          "description": "1 when completed delivery exists, otherwise 0",
                          "example": 1,
                          "maximum": 1,
                          "minimum": 0,
                          "type": "number"
                        },
                        "filled": {
                          "description": "Compatibility alias for completed impressions",
                          "example": 28430,
                          "type": "integer"
                        },
                        "no_fill": {
                          "description": "Always 0 on this completion-ledger report; use /stats for bid metrics",
                          "example": 0,
                          "type": "integer"
                        },
                        "period_days": {
                          "description": "Number of days covered by this report",
                          "example": 7,
                          "type": "integer"
                        },
                        "total_impressions": {
                          "description": "Canonical billable completed impressions for this DSP seat",
                          "example": 28430,
                          "type": "integer"
                        },
                        "unique_screens": {
                          "description": "Number of unique screens that completed an impression",
                          "example": 87,
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Performance summary for the specified period",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Aggregated performance summary",
        "tags": [
          "Reporting"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/reports/summary\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/reports/timeseries": {
      "get": {
        "description": "Returns canonical billable completed-impression data points for the\nauthenticated DSP seat at hourly or daily granularity.\n\n**Data source:** ClickHouse `mv_completed_impressions_hourly`, implicitly\nfiltered to `source_provider = inbound_dsp_<seat>`. Each point includes\ncompleted impressions, revenue-weighted average CPM, and active screens.\nA ClickHouse outage returns `data: []` with `200`.\n",
        "operationId": "getReportTimeseries",
        "parameters": [
          {
            "description": "Number of days to look back (default 7, maximum 90)",
            "example": 7,
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "default": 7,
              "maximum": 90,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Time bucket granularity. Use \"hourly\" for detailed intraday data\nor \"daily\" for longer-range trends (up to 90 days).\n",
            "example": "daily",
            "in": "query",
            "name": "granularity",
            "required": false,
            "schema": {
              "default": "hourly",
              "enum": [
                "hourly",
                "daily"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "active_screens": 72,
                      "avg_cpm": 7.35,
                      "impressions": 4120,
                      "timestamp": "2026-03-28T00:00:00.000Z"
                    },
                    {
                      "active_screens": 68,
                      "avg_cpm": 7.12,
                      "impressions": 3890,
                      "timestamp": "2026-03-29T00:00:00.000Z"
                    },
                    {
                      "active_screens": 74,
                      "avg_cpm": 7.58,
                      "impressions": 4350,
                      "timestamp": "2026-03-30T00:00:00.000Z"
                    },
                    {
                      "active_screens": 65,
                      "avg_cpm": 7.21,
                      "impressions": 3740,
                      "timestamp": "2026-03-31T00:00:00.000Z"
                    },
                    {
                      "active_screens": 79,
                      "avg_cpm": 7.68,
                      "impressions": 4510,
                      "timestamp": "2026-04-01T00:00:00.000Z"
                    },
                    {
                      "active_screens": 71,
                      "avg_cpm": 7.41,
                      "impressions": 3920,
                      "timestamp": "2026-04-02T00:00:00.000Z"
                    },
                    {
                      "active_screens": 70,
                      "avg_cpm": 7.49,
                      "impressions": 3900,
                      "timestamp": "2026-04-03T00:00:00.000Z"
                    }
                  ],
                  "granularity": "daily",
                  "period_days": 7,
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "description": "Chronological data points, ordered by timestamp ascending.\nEach point represents one time bucket (hour or day).\n",
                      "items": {
                        "properties": {
                          "active_screens": {
                            "description": "Number of screens that served at least one impression",
                            "example": 72,
                            "type": "integer"
                          },
                          "avg_cpm": {
                            "description": "Average CPM in this time bucket",
                            "example": 7.35,
                            "type": "number"
                          },
                          "impressions": {
                            "description": "Impressions in this time bucket",
                            "example": 4120,
                            "type": "integer"
                          },
                          "timestamp": {
                            "description": "Start of the time bucket in ISO 8601 format.\nFor daily granularity, this is midnight UTC.\n",
                            "example": "2026-03-28T00:00:00.000Z",
                            "format": "date-time",
                            "type": "string"
                          }
                        },
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "granularity": {
                      "description": "Time bucket granularity used",
                      "enum": [
                        "hourly",
                        "daily"
                      ],
                      "example": "daily",
                      "type": "string"
                    },
                    "period_days": {
                      "description": "Number of days covered",
                      "example": 7,
                      "type": "integer"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Chronological performance data",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Time-series performance data",
        "tags": [
          "Reporting"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/reports/timeseries\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/screens/audience": {
      "post": {
        "description": "Returns structured audience intelligence for the specified screens. Combines\nthree data sources with graceful degradation — any subset may be null without\nfailing the overall request:\n\n1. **Redis live signals** — fresh face count, attention, emotion, purchase intent,\n   and ad receptivity (10-30 s refresh).\n2. **VAS rolling averages** — Verified Attention Seconds, CPM tier, multiplier.\n3. **PostgreSQL venue enrichment** — neighborhood classification, Places rating,\n   amenity density, foot-traffic level.\n\n**Per-screen shape:**\n- `data_quality`: `live` when Redis has fresh data, `historical` when only\n  VAS is available, `none` when no audience data exists for the screen.\n- `signals_age_ms`: milliseconds since the last Redis update (null for non-live).\n- `demographics`, `behavior`, `venue` — filtered by `signal_types` if provided.\n- `vas` is always included (core DSP signal).\n- `patterns` is returned only when `include_patterns=true`. Currently reserved\n  for a future ClickHouse-backed hourly aggregation; returns `null` today.\n\n**Limits:** Maximum 50 screen IDs per request.\n\n**Rate limit category:** Inventory (200 req/min production).\n",
        "operationId": "getScreenAudience",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "include_live": true,
                "include_patterns": false,
                "screen_ids": [
                  "65a1b2c3d4e5f6a7b8c9d0e1",
                  "65a1b2c3d4e5f6a7b8c9d0e2"
                ],
                "signal_types": [
                  "demographics",
                  "behavior",
                  "venue"
                ]
              },
              "schema": {
                "properties": {
                  "include_live": {
                    "default": true,
                    "description": "Include fresh Redis signals. Defaults to `true`. Set `false` to\nskip the Redis query and force historical-only output.\n",
                    "example": true,
                    "type": "boolean"
                  },
                  "include_patterns": {
                    "default": false,
                    "description": "Request the `patterns` object (hourly breakdown). Reserved for a\nfuture ClickHouse-backed aggregation; returns `null` until shipped.\n",
                    "example": false,
                    "type": "boolean"
                  },
                  "screen_ids": {
                    "description": "Array of screen IDs (1-50 items).",
                    "example": [
                      "65a1b2c3d4e5f6a7b8c9d0e1",
                      "65a1b2c3d4e5f6a7b8c9d0e2"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 50,
                    "minItems": 1,
                    "type": "array"
                  },
                  "signal_types": {
                    "description": "Optional filter for returned sections. When omitted, all sections\n(`demographics`, `behavior`, `venue`) are included. `vas` is always\nreturned.\n",
                    "example": [
                      "demographics",
                      "behavior"
                    ],
                    "items": {
                      "enum": [
                        "demographics",
                        "behavior",
                        "venue"
                      ],
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "screen_ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "metadata": {
                      "total_screens": 2,
                      "with_historical_data": 1,
                      "with_live_data": 1,
                      "with_no_data": 0
                    },
                    "screens": [
                      {
                        "behavior": {
                          "ad_receptivity": 0.68,
                          "brand_mentions": [
                            "Starbucks"
                          ],
                          "dominant_emotion": "happy",
                          "emotional_engagement": 0.72,
                          "purchase_intent": "moderate",
                          "screen_engagement": 0.81,
                          "shopping_contexts": [
                            "coffee",
                            "breakfast"
                          ]
                        },
                        "data_quality": "live",
                        "demographics": {
                          "avg_attention_score": 0.76,
                          "avg_dwell_time_sec": 12,
                          "avg_face_count": 4.2,
                          "crowd_density": 12,
                          "group_composition": "couples",
                          "income_level": "medium"
                        },
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e1",
                        "signals_age_ms": 8200,
                        "vas": {
                          "cpm_multiplier": 1.4,
                          "score": 6.52,
                          "tier": "premium"
                        },
                        "venue": {
                          "foot_traffic_level": "high",
                          "nearby_amenity_count": 57,
                          "neighborhood_class": "dense_urban",
                          "places_rating": 4.6
                        }
                      },
                      {
                        "behavior": {
                          "ad_receptivity": null,
                          "brand_mentions": [],
                          "dominant_emotion": null,
                          "emotional_engagement": null,
                          "purchase_intent": null,
                          "screen_engagement": null,
                          "shopping_contexts": []
                        },
                        "data_quality": "historical",
                        "demographics": {
                          "avg_attention_score": 0.62,
                          "avg_dwell_time_sec": 0,
                          "avg_face_count": 2.1,
                          "crowd_density": null,
                          "group_composition": null,
                          "income_level": null
                        },
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e2",
                        "signals_age_ms": null,
                        "vas": {
                          "cpm_multiplier": 1,
                          "score": 4.18,
                          "tier": "standard"
                        },
                        "venue": {
                          "foot_traffic_level": null,
                          "nearby_amenity_count": 23,
                          "neighborhood_class": "suburban_commercial",
                          "places_rating": 4.2
                        }
                      }
                    ]
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "metadata": {
                          "properties": {
                            "total_screens": {
                              "example": 2,
                              "type": "integer"
                            },
                            "with_historical_data": {
                              "example": 1,
                              "type": "integer"
                            },
                            "with_live_data": {
                              "example": 1,
                              "type": "integer"
                            },
                            "with_no_data": {
                              "example": 0,
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        },
                        "screens": {
                          "items": {
                            "$ref": "#/components/schemas/ScreenAudienceProfile"
                          },
                          "type": "array"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Per-screen audience profiles.",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "example": 200,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "schema": {
                  "example": 199,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "screen_ids is required and must be a non-empty array",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request. Common causes: missing `screen_ids`, empty array,\nmore than 50 IDs, or invalid `signal_types` value.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Bulk audience profiles for up to 50 screens",
        "tags": [
          "Screens"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/screens/audience\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"screen_ids\":[\"65a1b2c3d4e5f6a7b8c9d0e1\",\"65a1b2c3d4e5f6a7b8c9d0e2\"],\"signal_types\":[\"demographics\",\"behavior\",\"venue\"],\"include_live\":true,\"include_patterns\":false}'\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/screens/availability": {
      "post": {
        "description": "Returns per-screen deliverable capacity, booked impressions, and remaining\navailability for a given date range. Use this to confirm there is enough\ninventory to satisfy your flight before you bid or book.\n\n**What you get per screen:**\n- **Capacity** — Total impressions deliverable in the window, plus spots, hours,\n  and a forecast confidence (0-1).\n- **Booked** — Impressions already committed to active/scheduled placements.\n- **Available** — Remaining impressions and utilization percentage.\n- **Reliability** — Whether the screen is currently online and its last heartbeat.\n- **Daily breakdown** (optional) — Per-day capacity/booked/available when\n  `granularity=daily` is provided.\n\n**Caching:** 5-minute in-memory cache keyed by the hash of the screen ID list\nand date range. Serving from cache returns `X-Cache: HIT`.\n\n**Limits:** Maximum 50 screen IDs per request. Returns a 503 if the forecast\nservice is unavailable.\n\n**Rate limit category:** Inventory (200 req/min production).\n",
        "operationId": "getScreenAvailability",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "end_date": "2026-05-14",
                "end_time": "21:00",
                "granularity": "daily",
                "screen_ids": [
                  "65a1b2c3d4e5f6a7b8c9d0e1",
                  "65a1b2c3d4e5f6a7b8c9d0e2"
                ],
                "start_date": "2026-05-01",
                "start_time": "09:00"
              },
              "schema": {
                "properties": {
                  "end_date": {
                    "description": "Flight end date (YYYY-MM-DD). Inclusive.",
                    "example": "2026-05-14",
                    "format": "date",
                    "type": "string"
                  },
                  "end_time": {
                    "description": "Optional daily end time (HH:MM, 24-hour). Defaults to `23:59`.\n",
                    "example": "21:00",
                    "type": "string"
                  },
                  "granularity": {
                    "default": "flight",
                    "description": "Response granularity:\n- `flight` — total capacity across the window (default)\n- `daily` — per-day breakdown in `daily_breakdown`\n",
                    "enum": [
                      "flight",
                      "daily"
                    ],
                    "example": "daily",
                    "type": "string"
                  },
                  "screen_ids": {
                    "description": "Array of screen IDs to check availability for (1-50 items).",
                    "example": [
                      "65a1b2c3d4e5f6a7b8c9d0e1",
                      "65a1b2c3d4e5f6a7b8c9d0e2"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 50,
                    "minItems": 1,
                    "type": "array"
                  },
                  "start_date": {
                    "description": "Flight start date (YYYY-MM-DD).",
                    "example": "2026-05-01",
                    "format": "date",
                    "type": "string"
                  },
                  "start_time": {
                    "description": "Optional daily start time (HH:MM, 24-hour). Defaults to `00:00`.\nUsed when you only want to measure capacity within a daypart.\n",
                    "example": "09:00",
                    "type": "string"
                  }
                },
                "required": [
                  "screen_ids",
                  "start_date",
                  "end_date"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "screens": [
                      {
                        "available": {
                          "impressions": 6300,
                          "spots": 6300,
                          "utilization_pct": 25
                        },
                        "booked": {
                          "active_placements": 2,
                          "impressions": 2100,
                          "reserved_impressions": 0
                        },
                        "capacity": {
                          "confidence": 0.92,
                          "hours": 168,
                          "impressions_per_hour": 50,
                          "total_impressions": 8400,
                          "total_spots": 8400
                        },
                        "daily_breakdown": [
                          {
                            "available_impressions": 450,
                            "booked_impressions": 150,
                            "date": "2026-05-01",
                            "reserved_impressions": 0,
                            "total_impressions": 600
                          }
                        ],
                        "name": "Downtown Coffee Shop Display",
                        "reliability": {
                          "last_seen": "2026-04-11T13:59:58.000Z",
                          "online_now": true
                        },
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e1",
                        "timezone": "America/New_York"
                      },
                      {
                        "available": {
                          "impressions": 6240,
                          "spots": 6240,
                          "utilization_pct": 17.5
                        },
                        "booked": {
                          "active_placements": 1,
                          "impressions": 1320,
                          "reserved_impressions": 0
                        },
                        "capacity": {
                          "confidence": 0.88,
                          "hours": 168,
                          "impressions_per_hour": 45,
                          "total_impressions": 7560,
                          "total_spots": 7560
                        },
                        "name": "Midtown Gym Lobby",
                        "reliability": {
                          "last_seen": "2026-04-11T13:59:12.000Z",
                          "online_now": true
                        },
                        "screen_id": "65a1b2c3d4e5f6a7b8c9d0e2",
                        "timezone": "America/New_York"
                      }
                    ],
                    "summary": {
                      "avg_utilization_pct": 21.3,
                      "screens_available": 2,
                      "screens_fully_booked": 0,
                      "total_available_impressions": 12540
                    }
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "screens": {
                          "items": {
                            "$ref": "#/components/schemas/ScreenAvailability"
                          },
                          "type": "array"
                        },
                        "summary": {
                          "properties": {
                            "avg_utilization_pct": {
                              "example": 28.4,
                              "type": "number"
                            },
                            "screens_available": {
                              "example": 2,
                              "type": "integer"
                            },
                            "screens_fully_booked": {
                              "example": 0,
                              "type": "integer"
                            },
                            "total_available_impressions": {
                              "example": 12540,
                              "type": "integer"
                            }
                          },
                          "type": "object"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Per-screen availability breakdown with summary totals.",
            "headers": {
              "RateLimit-Limit": {
                "schema": {
                  "example": 200,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "schema": {
                  "example": 199,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              },
              "X-Cache": {
                "description": "Cache status (`HIT` or `MISS`).",
                "schema": {
                  "enum": [
                    "HIT",
                    "MISS"
                  ],
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "screen_ids is required (array, 1-50 items)",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request. Common causes: missing `screen_ids`/`start_date`/`end_date`,\nempty `screen_ids` array, or more than 50 screen IDs.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Forecast service unavailable",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Forecast service unavailable. The downstream impression-forecast engine\nis offline; retry after a short delay.\n"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Bulk capacity and booking query",
        "tags": [
          "Screens"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/screens/availability\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"screen_ids\":[\"65a1b2c3d4e5f6a7b8c9d0e1\",\"65a1b2c3d4e5f6a7b8c9d0e2\"],\"start_date\":\"2026-05-01\",\"end_date\":\"2026-05-14\",\"start_time\":\"09:00\",\"end_time\":\"21:00\",\"granularity\":\"daily\"}'\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/screens/{id}": {
      "get": {
        "description": "Returns a full profile for a single screen — physical size, spot config,\nvenue intelligence, live + historical audience data, VAS scoring, 30-day\nperformance metrics, reliability, device capabilities, programmatic settings,\nand brand-safety flags.\n\n**Data sources (in order of latency):**\n1. **Redis** — live audience signals (~10 s refresh)\n2. **PostgreSQL** — screen metadata, venue enrichment, placements\n3. **ClickHouse** — performance rollups (30-day fill, completion, funnel)\n\n**Caching:** 2-minute in-memory cache keyed by `screen_id`. The `X-Cache`\nresponse header is `HIT` when served from cache, `MISS` otherwise.\n\n**Rate limit category:** Inventory (200 req/min production).\n",
        "operationId": "getScreenDetail",
        "parameters": [
          {
            "description": "Screen identifier — the `mongo_earnerscreen_id` (24-character MongoDB\nObjectId) returned by `/adslots` or `/inventory/v1/screens`. Must be\nat least 10 characters.\n",
            "example": "65a1b2c3d4e5f6a7b8c9d0e1",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "minLength": 10,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "audience": {
                      "coverage": "live",
                      "estimated_daily_impressions": 480,
                      "live": {
                        "ad_receptivity": 0.68,
                        "attention_score": 0.78,
                        "crowd_density": 12,
                        "data_quality": "live",
                        "dominant_emotion": "happy",
                        "emotional_engagement": 0.72,
                        "face_count": 4,
                        "last_updated": "2026-04-11T13:59:45.000Z",
                        "purchase_intent": "moderate",
                        "screen_engagement": 0.81
                      },
                      "vas": {
                        "avg_raw": 5.48,
                        "avg_weighted": 6.52,
                        "cpm_multiplier": 1.4,
                        "last_measurement": "2026-04-11T13:58:00.000Z",
                        "median": 5.1,
                        "p90": 9.8,
                        "tier": "premium",
                        "total_measurements": 4820
                      }
                    },
                    "brand_safety": {
                      "allowed_sensitive": {
                        "adult": false,
                        "alcohol": false,
                        "cannabis": false,
                        "gambling": false,
                        "political": false,
                        "tobacco": false
                      },
                      "blocked_categories": [],
                      "content_filtering": true,
                      "max_age_rating": "PG-13"
                    },
                    "device": {
                      "capabilities": {
                        "audio_classification": false,
                        "face_detection": true,
                        "sound_enabled": true
                      },
                      "connection_type": "wifi",
                      "device_type": 8,
                      "make": "Amazon",
                      "model": "Fire TV Stick 4K Max",
                      "os": "android",
                      "os_version": "11",
                      "video": {
                        "max_duration_sec": 30,
                        "mimes": [
                          "video/mp4"
                        ],
                        "min_duration_sec": 6,
                        "protocols": [
                          2,
                          3,
                          5,
                          6
                        ]
                      }
                    },
                    "display": {
                      "height_px": 1080,
                      "orientation": "landscape",
                      "sound_enabled": true,
                      "width_px": 1920
                    },
                    "location": {
                      "city": "New York",
                      "country": "US",
                      "lat": 40.7128,
                      "lng": -74.006,
                      "state": "NY",
                      "timezone": "America/New_York",
                      "zip": "10001"
                    },
                    "name": "Downtown Coffee Shop Display",
                    "performance": {
                      "completion_rate": 0.942,
                      "fill_rate": 0.867,
                      "total_completions_30d": 13423,
                      "total_impressions_30d": 14250,
                      "vast_funnel": {
                        "completions": 13423,
                        "errors": 185,
                        "fills": 14250,
                        "requests": 16430
                      }
                    },
                    "physical_size": {
                      "confidence": "estimated",
                      "diagonal_inches": 43,
                      "height_inches": 21.1,
                      "width_inches": 37.5
                    },
                    "programmatic": {
                      "allocation_pct": 100,
                      "auction_enabled": true,
                      "auction_type": "first_price",
                      "dynamic_floor_pricing": false,
                      "floor_cpm": 2.5,
                      "header_bidding": {
                        "enabled": false
                      }
                    },
                    "reliability": {
                      "last_seen": "2026-04-11T13:59:58.000Z",
                      "online_now": true
                    },
                    "screen_id": "65a1b2c3d4e5f6a7b8c9d0e1",
                    "spot_config": {
                      "ad_interval_sec": 30,
                      "concurrent_ad_slots": 1,
                      "display_mode": "fullscreen",
                      "impressions_per_hour": 120,
                      "min_spot_duration_sec": 6,
                      "programmatic_share": 1,
                      "spot_duration_sec": 15,
                      "spots_per_hour": 120
                    },
                    "venue": {
                      "environment": "indoor",
                      "grandchild": null,
                      "nearby_amenities": {
                        "bars": 6,
                        "cafes": 9,
                        "gyms": 2,
                        "restaurants": 14,
                        "shops": 22,
                        "theatres": 1,
                        "total": 57,
                        "transit_stops": 3
                      },
                      "nearby_amenity_count": 57,
                      "neighborhood_class": "dense_urban",
                      "placement": "counter",
                      "places_price_level": 2,
                      "places_rating": 4.6,
                      "places_review_count": 1245,
                      "quality_score": 0.87,
                      "subcategory": "coffee_shop",
                      "type": "retail"
                    }
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ScreenProfile"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Full screen profile with all enrichment sections populated.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 200,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 198,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 45,
                  "type": "integer"
                }
              },
              "X-Cache": {
                "description": "Cache status (`HIT` from in-memory cache, `MISS` fresh).",
                "schema": {
                  "enum": [
                    "HIT",
                    "MISS"
                  ],
                  "example": "MISS",
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Invalid screen ID",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid screen ID. The path parameter must be at least 10 characters.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Screen not found",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Screen not found. The screen either does not exist or has been soft-deleted.\n"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Comprehensive screen profile",
        "tags": [
          "Screens"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/screens/65a1b2c3d4e5f6a7b8c9d0e1\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/stats": {
      "get": {
        "description": "Returns aggregated performance metrics for the authenticated DSP over the\nlast 24 hours.\n\n**Metrics returned:**\n- Total bids submitted\n- Total auction wins\n- Average CPM (clearing price)\n- Total revenue in USD\n- Average bid-to-win latency in milliseconds\n\n## A metric can be `null`, and `null` is not zero\n\nEvery metric is nullable. `null` means **not measured** — it is never a\nstand-in for zero, and the response never substitutes a placeholder value\nfor a missing one. Branch on the top-level `measured` boolean before you\nread a number, and read `stats.coverage` for the reason when it is\n`false`:\n\n| `stats.coverage` | Meaning |\n| --- | --- |\n| `measured` | Real figures. `matched_auctions` is the row count behind them. |\n| `seat_absent_from_auction_metadata` | No auction on our side is attributed to this seat in the window, so there is nothing to total. |\n| `clickhouse_unavailable` | We could not read the warehouse. This is our outage, not a statement about your bidding. |\n\nThis replaces earlier behaviour in which an empty or failed read returned\nfive zeros with a `200`, which read as a measurement (\"you bid zero\ntimes\") when it was in fact the absence of one.\n",
        "operationId": "getDSPStats",
        "parameters": [
          {
            "description": "DSP seat identifier. If omitted, auto-filled from the authenticated\nAPI key. Only specify this if your API key has access to multiple seats.\n",
            "example": "acme_ads",
            "in": "query",
            "name": "seat",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "examples": {
                  "measured": {
                    "summary": "Real figures",
                    "value": {
                      "measured": true,
                      "period": "24h",
                      "seat": "acme_ads",
                      "stats": {
                        "avg_cpm": 7.42,
                        "avg_latency_ms": 34.7,
                        "coverage": "measured",
                        "matched_auctions": 14523,
                        "total_bids": 14523,
                        "total_revenue": 28508.22,
                        "total_wins": 3841
                      }
                    }
                  },
                  "not_measured": {
                    "summary": "Nothing attributed to this seat in the window",
                    "value": {
                      "measured": false,
                      "period": "24h",
                      "seat": "acme_ads",
                      "stats": {
                        "avg_cpm": null,
                        "avg_latency_ms": null,
                        "coverage": "seat_absent_from_auction_metadata",
                        "matched_auctions": 0,
                        "total_bids": null,
                        "total_revenue": null,
                        "total_wins": null
                      }
                    }
                  }
                },
                "schema": {
                  "properties": {
                    "measured": {
                      "description": "`true` only when the figures are a real measurement. When\n`false`, every metric is `null` and `stats.coverage` gives\nthe reason.\n",
                      "example": true,
                      "type": "boolean"
                    },
                    "period": {
                      "description": "Reporting period",
                      "example": "24h",
                      "type": "string"
                    },
                    "seat": {
                      "description": "DSP seat identifier",
                      "example": "acme_ads",
                      "type": "string"
                    },
                    "stats": {
                      "description": "Aggregated performance metrics",
                      "properties": {
                        "avg_cpm": {
                          "description": "Average clearing CPM in USD. `null` when not measured.",
                          "example": 7.42,
                          "nullable": true,
                          "type": "number"
                        },
                        "avg_latency_ms": {
                          "description": "Average bid-to-win processing latency in milliseconds. `null` when not measured.",
                          "example": 34.7,
                          "nullable": true,
                          "type": "number"
                        },
                        "coverage": {
                          "description": "Why the metrics read as they do. `measured` means real\nfigures; any other value means every metric is `null`.\n",
                          "enum": [
                            "measured",
                            "seat_absent_from_auction_metadata",
                            "clickhouse_unavailable"
                          ],
                          "example": "measured",
                          "type": "string"
                        },
                        "matched_auctions": {
                          "description": "Auction rows attributed to this seat in the window. `0`\nis why the metrics are `null` — it is a row count, not\na performance figure.\n",
                          "example": 14523,
                          "type": "integer"
                        },
                        "total_bids": {
                          "description": "Total bids submitted in the period. `null` when not measured.",
                          "example": 14523,
                          "nullable": true,
                          "type": "integer"
                        },
                        "total_revenue": {
                          "description": "Total revenue in USD. `null` when not measured.",
                          "example": 28508.22,
                          "nullable": true,
                          "type": "number"
                        },
                        "total_wins": {
                          "description": "Total auction wins. `null` when not measured.",
                          "example": 3841,
                          "nullable": true,
                          "type": "integer"
                        }
                      },
                      "type": "object"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "24-hour performance metrics",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get 24-hour performance stats",
        "tags": [
          "Reporting"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/stats\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/webhooks": {
      "get": {
        "description": "Returns all webhooks for your DSP seat, ordered by creation time descending.\n**Secrets are never returned** by this endpoint — they are only shown once at\ncreation time.\n\nEach entry includes delivery stats (`success_count`, `failure_count`,\n`last_triggered_at`, `last_error`) so you can monitor webhook health.\n\n**Rate limit category:** Campaigns (100 req/min).\n",
        "operationId": "listWebhooks",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [
                    {
                      "created_at": "2026-04-11T14:00:00.000Z",
                      "description": "AdQuick staging pipeline",
                      "events": [
                        "impression.delivered",
                        "campaign.completed",
                        "screen.offline"
                      ],
                      "failure_count": 1,
                      "last_error": null,
                      "last_triggered_at": "2026-04-11T14:05:12.000Z",
                      "status": "active",
                      "success_count": 42,
                      "updated_at": "2026-04-11T14:00:00.000Z",
                      "url": "https://acme-ads.com/webhooks/trillboards",
                      "webhook_id": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC"
                    }
                  ],
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/WebhookSubscription"
                      },
                      "type": "array"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "List of webhooks for the current DSP seat.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List webhook subscriptions for this DSP seat",
        "tags": [
          "DSP Webhooks"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/webhooks\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      },
      "post": {
        "description": "Registers a new webhook for your DSP seat. Returns a unique `whsec_...`\nsecret **exactly once** — store it immediately. The secret is used to\nverify the `X-Trillboards-Signature` header on every delivery.\n\n**URL must be HTTPS.** HTTP URLs, missing hostnames, and non-URL strings are\nrejected.\n\n**Events must be from the supported list.** See the `events` enum below; any\nunknown event name is rejected with a 400 listing the allowed values.\n\n**Limits:** Maximum 10 active webhooks per DSP seat. Exceeding this returns\n429 with the current count.\n\n**Rate limit category:** Campaigns (100 req/min).\n",
        "operationId": "registerWebhook",
        "requestBody": {
          "content": {
            "application/json": {
              "example": {
                "description": "AdQuick staging pipeline",
                "events": [
                  "impression.delivered",
                  "campaign.completed",
                  "screen.offline"
                ],
                "url": "https://acme-ads.com/webhooks/trillboards"
              },
              "schema": {
                "properties": {
                  "description": {
                    "description": "Optional human-readable note to identify the webhook in the list\nview (e.g., \"Staging environment\", \"Billing pipeline\").\n",
                    "example": "AdQuick staging pipeline",
                    "type": "string"
                  },
                  "events": {
                    "description": "Non-empty list of event types to subscribe to. All entries must\nmatch one of the supported event names.\n",
                    "example": [
                      "impression.delivered",
                      "campaign.completed",
                      "screen.offline"
                    ],
                    "items": {
                      "$ref": "#/components/schemas/WebhookEvent"
                    },
                    "minItems": 1,
                    "type": "array"
                  },
                  "url": {
                    "description": "HTTPS endpoint that will receive webhook deliveries.",
                    "example": "https://acme-ads.com/webhooks/trillboards",
                    "format": "uri",
                    "type": "string"
                  }
                },
                "required": [
                  "url",
                  "events"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "created_at": "2026-04-11T14:00:00.000Z",
                    "description": "AdQuick staging pipeline",
                    "events": [
                      "impression.delivered",
                      "campaign.completed",
                      "screen.offline"
                    ],
                    "secret": "whsec_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
                    "status": "active",
                    "url": "https://acme-ads.com/webhooks/trillboards",
                    "webhook_id": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "created_at": {
                          "example": "2026-04-11T14:00:00.000Z",
                          "format": "date-time",
                          "type": "string"
                        },
                        "description": {
                          "example": "AdQuick staging pipeline",
                          "nullable": true,
                          "type": "string"
                        },
                        "events": {
                          "items": {
                            "$ref": "#/components/schemas/WebhookEvent"
                          },
                          "type": "array"
                        },
                        "secret": {
                          "description": "HMAC-SHA256 signing secret. Prefix `whsec_` followed by 32\nhex characters. Shown **exactly once** — store immediately.\n",
                          "example": "whsec_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
                          "type": "string"
                        },
                        "status": {
                          "enum": [
                            "active"
                          ],
                          "example": "active",
                          "type": "string"
                        },
                        "url": {
                          "example": "https://acme-ads.com/webhooks/trillboards",
                          "type": "string"
                        },
                        "webhook_id": {
                          "example": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Webhook registered successfully. The `secret` field is returned **exactly\nonce** — store it in a secrets manager now.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "url must use https:// scheme",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Invalid request. Common causes: missing/malformed `url`, non-HTTPS URL,\nempty `events` array, or unknown event name.\n"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Maximum 10 webhooks per DSP seat. Currently: 10",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Quota exceeded. Either the campaigns rate limit was hit, or the DSP seat\nalready has 10 active webhooks.\n"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Database write unavailable",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Database write unavailable."
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Register a webhook subscription",
        "tags": [
          "DSP Webhooks"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X POST \\\n  \"https://api.trillboards.com/openrtb/v2/webhooks\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"https://acme-ads.com/webhooks/trillboards\",\"events\":[\"impression.delivered\",\"campaign.completed\",\"screen.offline\"],\"description\":\"AdQuick staging pipeline\"}'\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/webhooks/deliveries": {
      "get": {
        "description": "Returns recent delivery attempts for this DSP seat's webhooks. Each delivery\nrecord captures the event name, target URL, HTTP response code, response\nbody sample, signature, and timestamp. Use this to debug failing deliveries\nwithout having to instrument your own receiver.\n\n**Note:** The delivery log table is being finalized in a follow-up release.\nUntil it ships, this endpoint returns an empty `data` array and a `note`\nfield indicating the stub state. Schema is stable — consumers can integrate\nnow and will begin receiving records when the backing table is live.\n\n**Rate limit category:** Campaigns (100 req/min).\n",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "description": "Filter to a single webhook ID.",
            "example": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
            "in": "query",
            "name": "webhook_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Delivery status filter.\n",
            "example": "failed",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "default": "all",
              "enum": [
                "failed",
                "success",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "description": "Max records to return (default 20, maximum 100).",
            "example": 20,
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Pagination offset.",
            "example": 0,
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": [],
                  "note": "Delivery log coming in future PR",
                  "pagination": {
                    "limit": 20,
                    "offset": 0,
                    "status": "all",
                    "webhook_id": null
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      },
                      "type": "array"
                    },
                    "note": {
                      "description": "Present while the delivery log table is being finalized. Will\nbe removed once the backing table ships.\n",
                      "example": "Delivery log coming in future PR",
                      "nullable": true,
                      "type": "string"
                    },
                    "pagination": {
                      "properties": {
                        "limit": {
                          "example": 20,
                          "type": "integer"
                        },
                        "offset": {
                          "example": 0,
                          "type": "integer"
                        },
                        "status": {
                          "example": "all",
                          "type": "string"
                        },
                        "webhook_id": {
                          "example": null,
                          "nullable": true,
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Delivery log entries. Returns an empty `data` array and a `note` field\nuntil the backing table is finalized in a follow-up release.\n",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Self-serve webhook delivery log",
        "tags": [
          "DSP Webhooks"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS \"https://api.trillboards.com/openrtb/v2/webhooks/deliveries\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    },
    "/openrtb/v2/webhooks/{id}": {
      "delete": {
        "description": "Deletes a webhook subscription. Ownership is verified against the\nauthenticated DSP seat — only the seat that created the webhook can delete\nit. Returns 404 if the webhook does not exist.\n\n**Rate limit category:** Campaigns (100 req/min).\n",
        "operationId": "deleteWebhook",
        "parameters": [
          {
            "description": "Webhook ID to delete.",
            "example": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "status": "deleted",
                    "webhook_id": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC"
                  },
                  "success": true
                },
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "status": {
                          "enum": [
                            "deleted"
                          ],
                          "example": "deleted",
                          "type": "string"
                        },
                        "webhook_id": {
                          "example": "wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC",
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "success": {
                      "example": true,
                      "type": "boolean"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Webhook deleted.",
            "headers": {
              "RateLimit-Limit": {
                "description": "Maximum requests allowed in the current window",
                "schema": {
                  "example": 100,
                  "type": "integer"
                }
              },
              "RateLimit-Remaining": {
                "description": "Requests remaining in the current window",
                "schema": {
                  "example": 99,
                  "type": "integer"
                }
              },
              "RateLimit-Reset": {
                "description": "Seconds until the rate limit window resets",
                "schema": {
                  "example": 58,
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "content": {
              "application/json": {
                "example": {
                  "error": "webhook id required",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Missing webhook ID."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Not authorized to delete this webhook",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Webhook not owned by this DSP seat."
          },
          "404": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Webhook not found",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Webhook not found."
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          },
          "503": {
            "content": {
              "application/json": {
                "example": {
                  "error": "Database write unavailable",
                  "success": false
                },
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            },
            "description": "Database write unavailable."
          }
        },
        "security": [
          {
            "BearerAuth": []
          },
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Remove a webhook subscription",
        "tags": [
          "DSP Webhooks"
        ],
        "x-codeSamples": [
          {
            "label": "curl",
            "lang": "Shell",
            "source": "curl -sS -X DELETE \\\n  \"https://api.trillboards.com/openrtb/v2/webhooks/wh_01HS8A3T0X7V4Q9P2MWKJ5N6BC\" \\\n  -H \"Authorization: Bearer $TRILLBOARDS_API_KEY\"\n"
          }
        ],
        "x-trillboards-agent-safe": false,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "dsp"
      }
    }
  },
  "servers": [
    {
      "description": "Production API",
      "url": "https://api.trillboards.com"
    }
  ],
  "tags": [
    {
      "description": "Self-serve DSP registration and API key management.\n\nRegister in a single API call — no manual approval required. You receive an API key\nimmediately and start in sandbox mode with full API access. Sandbox bids are validated\nand scored but do not enter the live auction, so you can integrate safely.\n\n**Lifecycle:** sandbox -> approved -> (suspended | revoked)\n\nTo upgrade from sandbox to production, contact developers@trillboards.com with your\nseat ID and expected daily bid volume. Production DSPs get 10x higher rate limits and\ntheir bids enter the live waterfall auction.\n",
      "name": "Onboarding"
    },
    {
      "description": "Real-time DOOH inventory discovery with FEIN edge AI signals.\n\nBrowse available screens as OpenRTB 2.6 bid request objects, each enriched with live\naudience data from on-device computer vision and audio analysis. No other SSP provides\nreal-time face count, gaze attention, emotion detection, and purchase intent signals\ndirectly in the bid request.\n\n**Three discovery modes:**\n- **`GET /adslots`** — Structured filtering by venue type, geography, and FEIN thresholds\n- **`POST /discover`** — Semantic search using natural language (powered by pgvector embeddings)\n- **`POST /forecast`** — Budget-based delivery estimation using 7-day ClickHouse historical data\n",
      "name": "Inventory"
    },
    {
      "description": "OpenRTB 2.6 real-time bidding.\n\nSubmit standard OpenRTB 2.6 bid responses. Each bid must include an impression ID (`impid`),\na price in CPM, and either inline ad markup (`adm`) or a win notice URL (`nurl`).\n\nBids are validated against creative blocklists and floor prices, then stored for the\nlive waterfall auction. The response includes per-bid acceptance or rejection with\nspecific reasons (below_floor, creative_blocked, missing_adm_and_nurl).\n\n**Win/loss notifications** are sent to your `notification_url` (if configured during\nonboarding) as POST requests with the bid ID, win price, and screen details.\n",
      "name": "Bidding"
    },
    {
      "description": "Creative upload with automatic Gemini AI moderation.\n\nUpload creatives via URL reference or presigned S3 upload. Every creative is automatically\nprocessed by the Gemini AI moderation pipeline, which produces:\n\n- **Content classification** — 8 safety flags (alcohol, tobacco, gambling, etc.)\n- **Age rating** — G, PG, PG-13, R, NC-17\n- **Brand detection** — Identifies known brands in creative content\n- **IAB categories** — IAB Content Taxonomy 1.0 codes for programmatic targeting\n- **Content analysis** — Subjects, setting, mood, quality, keywords\n\nModeration completes in approximately 30 seconds. Poll `GET /creatives/{id}` to check\nstatus. Only `approved` creatives can be used in campaigns.\n",
      "name": "Creatives"
    },
    {
      "description": "Campaign creation and screen assignment.\n\nCreate campaigns by linking approved creatives to targeting criteria. The API returns\nmatching screens based on your targeting, then you assign the creative to specific\nscreens. Screens pick up new assignments within their content refresh cycle\n(approximately 30-60 seconds).\n\n**Workflow:**\n1. `POST /campaigns` — Create campaign with targeting, get matching screens\n2. `POST /campaigns/assign` — Assign creative to specific screen IDs\n3. Screens begin showing the creative within 30-60 seconds\n",
      "name": "Campaigns"
    },
    {
      "description": "Private marketplace (PMP) and preferred deal management.\n\nBrowse active deals available to your DSP seat, view deal details, and propose new\ndeals with custom floor CPMs, impression caps, and venue/geography targeting.\n\n**Deal types:**\n- **PMP** — Private marketplace with floor price, open to invited buyers\n- **Preferred** — Preferred deal with fixed price, priority over open auction\n- **Guaranteed** — Guaranteed delivery with committed volume\n\nProposed deals require admin approval and transition from `pending` to `active` status.\n",
      "name": "Deals"
    },
    {
      "description": "ClickHouse-backed analytics and reporting.\n\nAll reporting endpoints query ClickHouse materialized views for sub-second response\ntimes across billions of impression events. Three report types are available:\n\n- **Summary** — Aggregated totals: impressions, unique screens, CPM, fill rate\n- **Per-screen** — Breakdown by screen: impressions, CPM, fills, hours active\n- **Time-series** — Chronological data points at hourly or daily granularity\n\nReports cover a configurable lookback period (default 7 days, maximum 90 days).\n",
      "name": "Reporting"
    },
    {
      "description": "Universal inventory discovery API for querying the full Trillboards screen network.\n\nThe `/inventory/v1/screens` endpoint provides a structured, filterable view of all\nauction-enabled screens with location, venue, display, device, programmatic, and\ncontent-rule metadata. Results are cached at the edge with `X-Cache: HIT/MISS`\nheaders for efficient polling.\n\n**Use this endpoint to:**\n- Build a screen catalog for media planning\n- Filter by geography, venue type, floor CPM range, and online status\n- Retrieve display specs, device capabilities, and content restrictions\n- Paginate through large inventories with `limit` and `offset`\n",
      "name": "Inventory Discovery"
    },
    {
      "description": "IAB supply chain transparency endpoints.\n\nTrillboards publishes standard IAB sellers.json and ads.txt files for supply chain\nverification. These endpoints are public (no authentication required) and cached\nfor 24 hours.\n\n- **`/sellers.json`** — IAB OpenRTB sellers.json listing all authorized sellers\n- **`/ads.txt`** — Redirects to `/app-ads.txt` (canonical source)\n- **`/app-ads.txt`** — Authorized digital sellers declarations\n",
      "name": "Supply Chain"
    },
    {
      "description": "Deep screen intelligence — detail profile, capacity forecasting, and audience profiles.\n\nThese endpoints support direct IO booking, pre-auction targeting, and pre-bid\ndecisioning. Unlike `/adslots` (which returns OpenRTB bid requests), these surfaces\nare purpose-built for structured screen-by-screen queries.\n\n- **`GET /screens/:id`** — Full profile: physical size, spot config, venue intelligence,\n  VAS scoring, performance rollups, reliability, device capabilities, brand safety.\n- **`POST /screens/availability`** — Bulk capacity forecast + booked impressions\n  for up to 50 screens and an arbitrary date range. Returns deliverable capacity,\n  booked impressions, and remaining availability per screen.\n- **`POST /screens/audience`** — Bulk audience profiles for up to 50 screens,\n  combining live Redis signals (data_quality=live) with VAS rolling averages\n  (data_quality=historical).\n",
      "name": "Screens"
    },
    {
      "description": "CPM benchmarks, demand curves, and segment premium analysis for pre-bid pricing.\n\nData is sourced from PG rollup tables (refreshed every 5 minutes from ClickHouse\nmaterialized views). Queries return in <100 ms and cover configurable lookback\nwindows up to 365 days.\n\n- **`GET /market/cpm-benchmarks`** — p25/median/p75/avg CPM percentiles by venue\n  type, country, state, and daypart. Includes a market overview with total\n  SSP-enabled screens, average fill rate, and average completion rate.\n- **`GET /market/demand-analysis`** — Top advertiser categories by bid volume,\n  daypart demand curves (avg CPM + fill rate by morning/afternoon/evening/late_night),\n  and segment CPM premium lift over baseline.\n",
      "name": "Market Intelligence"
    },
    {
      "description": "VAST event funnel — from ad requests to completed plays.\n\nReturns a request → bid_received → render_started → play_completed funnel with\nbid rate, fill rate, and start-to-complete rate. Supports per-screen breakdown\n(top 50) and daily/hourly/flight timeseries.\n\n**Data source:** `openrtb_vast_screen_hourly` PG rollup table (OLTP-safe, refreshed\nevery 5 minutes from ClickHouse). Cached in memory for 5 minutes per query\nfingerprint.\n",
      "name": "Analytics"
    },
    {
      "description": "Verified ad play records for reconciliation and billing audit.\n\nReturns rows from the `completed_impressions` table (partitioned monthly on\n`completed_at`) with verification tier mapping:\n\n- **`signed`** — Ed25519 signed, screen-side proof-of-play confirmation\n- **`event_confirmed`** — VAST `complete` event fired by the IMA SDK\n- **`impression_callback`** — Impression pixel fired by the screen\n\nAll queries must include `start_date` and `end_date` (required for partition\npruning). Responses include per-tier counts, GIVT pass rate, and paginated\nproof records.\n",
      "name": "Proof of Play"
    },
    {
      "description": "Managed webhook subscription system for real-time event notifications.\n\nRegister HTTPS endpoints with a filtered list of event types. Trillboards\ndispatches signed HTTPS POSTs as events occur across the ad lifecycle (impression,\ncampaign, screen). Each webhook receives a unique `whsec_...` secret\nat creation — **shown exactly once** — that must be used to verify the\n`X-Trillboards-Signature` header on delivery.\n\n- **`POST /webhooks`** — Register a webhook subscription (URL + events).\n- **`GET /webhooks`** — List all webhooks for your DSP seat (without secrets).\n- **`GET /webhooks/deliveries`** — Self-serve delivery log (recent attempts + failures).\n- **`DELETE /webhooks/:id`** — Remove a webhook subscription. Ownership verified.\n\n**Limits:** Max 10 active webhooks per DSP seat. URL must use HTTPS. Only events\nfrom the supported event list are accepted.\n",
      "name": "DSP Webhooks"
    },
    {
      "description": "Core OpenRTB bidding and win-notification flows",
      "name": "Programmatic Bidding"
    },
    {
      "description": "Impression, proof-of-play, and viewability collection",
      "name": "Impression Tracking"
    },
    {
      "description": "Completion, heartbeat, performance, and event analytics",
      "name": "Event Tracking"
    },
    {
      "description": "Interaction and QR scan analytics",
      "name": "Interaction Tracking"
    },
    {
      "description": "VAST request outcome telemetry, creative blocklists, and cache analytics",
      "name": "VAST Telemetry"
    },
    {
      "description": "Video cache and bandwidth savings telemetry",
      "name": "Bandwidth Telemetry"
    },
    {
      "description": "Manual review and creative history endpoints",
      "name": "Creative Review"
    },
    {
      "description": "Experimentation and creative performance analytics",
      "name": "Creative Intelligence"
    },
    {
      "description": "OMSDK verification session and reporting routes",
      "name": "OMSDK"
    },
    {
      "description": "Lookalikes, prediction, semantic search, and creative ranking",
      "name": "Audience Intelligence"
    },
    {
      "description": "FEIN edge AI analytics and readiness reporting",
      "name": "FEIN Analytics"
    },
    {
      "description": "FEIN webhook registration and SSE streaming",
      "name": "FEIN Webhooks"
    },
    {
      "description": "Content performance, recommendations, semantic search, and audience correlation",
      "name": "Content Intelligence"
    },
    {
      "description": "SQL-owned OpenRTB bidder endpoint where Trillboards buys partner exchange inventory",
      "name": "Partner Exchange Bidder"
    },
    {
      "description": "",
      "name": "Fleet Management"
    },
    {
      "description": "",
      "name": "Internal"
    },
    {
      "description": "",
      "name": "Sandbox"
    }
  ],
  "x-trillboards-bundle": {
    "slug": "demand",
    "sources": [
      {
        "file": "dsp-api.yaml",
        "key": "dsp"
      },
      {
        "file": "programmatic-api.yaml",
        "key": "programmatic"
      }
    ]
  }
}
