{
  "components": {
    "responses": {
      "Error": {
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        },
        "description": "Canonical Trillboards error envelope."
      }
    },
    "schemas": {
      "DataApi.AggregateResponse": {
        "properties": {
          "aggregations": {
            "items": {
              "additionalProperties": {},
              "type": "object"
            },
            "type": "array"
          },
          "group_by": {
            "type": "string"
          },
          "metric": {
            "type": "string"
          },
          "time_window": {
            "type": "string"
          }
        },
        "required": [
          "group_by",
          "metric",
          "time_window",
          "aggregations"
        ],
        "type": "object"
      },
      "DataApi.LookalikeResponse": {
        "properties": {
          "matches": {
            "items": {
              "additionalProperties": {},
              "type": "object"
            },
            "type": "array"
          },
          "matches_count": {
            "minimum": 0,
            "type": "integer"
          },
          "source_screen_id": {
            "type": "string"
          }
        },
        "required": [
          "source_screen_id",
          "matches_count",
          "matches"
        ],
        "type": "object"
      },
      "DataApi.QueryDslBody": {
        "properties": {
          "dataset": {
            "description": "Dataset name. Defaults to `inference_outcomes`.",
            "example": "inference_outcomes",
            "type": "string"
          },
          "dimensions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "filters": {
            "items": {
              "$ref": "#/components/schemas/DataApi.QueryFilter"
            },
            "type": "array"
          },
          "limit": {
            "maximum": 500,
            "minimum": 1,
            "type": "integer"
          },
          "measures": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "order_by": {
            "items": {
              "$ref": "#/components/schemas/DataApi.QueryOrderBy"
            },
            "type": "array"
          }
        },
        "type": "object"
      },
      "DataApi.QueryDslResponse": {
        "properties": {
          "count": {
            "minimum": 0,
            "type": "integer"
          },
          "coverage": {
            "description": "`observation_stream` when it is served from ClickHouse: the population the answer counts and the first instant the dataset holds.",
            "properties": {
              "population": {
                "type": "string"
              },
              "since": {
                "type": "string"
              }
            },
            "required": [
              "population",
              "since"
            ],
            "type": "object"
          },
          "dataset": {
            "type": "string"
          },
          "dimensions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "inference_source": {
            "description": "`inference_outcomes` only. The `inference_source` values the query read (`null` when a range filter makes the set unstateable), and whether the camera-only default was applied because the request did not filter on `inference_source`.",
            "properties": {
              "defaulted": {
                "type": "boolean"
              },
              "values": {
                "items": {
                  "type": "string"
                },
                "nullable": true,
                "type": "array"
              }
            },
            "required": [
              "values",
              "defaulted"
            ],
            "type": "object"
          },
          "limit": {
            "minimum": 1,
            "type": "integer"
          },
          "measures": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "provenance": {
            "description": "`inference_outcomes` only. `measured` when every row read is a camera measurement, `modeled` when every row is a venue-profile estimate, `mixed` when the answer aggregates both.",
            "enum": [
              "measured",
              "modeled",
              "mixed"
            ],
            "type": "string"
          },
          "rows": {
            "description": "Result rows. For `inference_outcomes` every row also carries `provenance` (`measured`, `modeled` or `mixed`).",
            "items": {
              "additionalProperties": {},
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "dataset",
          "dimensions",
          "measures",
          "rows",
          "count",
          "limit"
        ],
        "type": "object"
      },
      "DataApi.QueryFilter": {
        "properties": {
          "field": {
            "minLength": 1,
            "type": "string"
          },
          "op": {
            "enum": [
              "eq",
              "in",
              "gte",
              "lte",
              "between"
            ],
            "type": "string"
          },
          "value": {}
        },
        "required": [
          "field"
        ],
        "type": "object"
      },
      "DataApi.QueryOrderBy": {
        "properties": {
          "direction": {
            "enum": [
              "asc",
              "desc",
              "ASC",
              "DESC"
            ],
            "type": "string"
          },
          "field": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "field"
        ],
        "type": "object"
      },
      "DataApi.ScreenAudienceResponse": {
        "properties": {
          "audience": {
            "additionalProperties": {},
            "nullable": true,
            "type": "object"
          },
          "message": {
            "type": "string"
          },
          "screen_id": {
            "type": "string"
          },
          "source": {
            "type": "string"
          }
        },
        "required": [
          "screen_id",
          "audience"
        ],
        "type": "object"
      },
      "DataApi.ScreenHistoryResponse": {
        "properties": {
          "count": {
            "minimum": 0,
            "type": "integer"
          },
          "metrics": {
            "items": {
              "additionalProperties": {},
              "type": "object"
            },
            "type": "array"
          },
          "screen_id": {
            "type": "string"
          },
          "source": {
            "enum": [
              "camera",
              "signal_derived"
            ],
            "type": "string"
          }
        },
        "required": [
          "screen_id",
          "metrics",
          "count"
        ],
        "type": "object"
      },
      "DataApi.ScreensListResponse": {
        "properties": {
          "count": {
            "minimum": 0,
            "type": "integer"
          },
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "minimum": 1,
            "type": "integer"
          },
          "next_offset": {
            "minimum": 0,
            "nullable": true,
            "type": "integer"
          },
          "offset": {
            "minimum": 0,
            "type": "integer"
          },
          "screens": {
            "items": {
              "additionalProperties": {},
              "type": "object"
            },
            "type": "array"
          },
          "total": {
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "screens",
          "count",
          "total",
          "offset",
          "limit",
          "has_more",
          "next_offset"
        ],
        "type": "object"
      },
      "DataApi.SemanticSearchResponse": {
        "properties": {
          "query": {
            "type": "string"
          },
          "results": {
            "items": {
              "additionalProperties": {},
              "type": "object"
            },
            "type": "array"
          },
          "results_count": {
            "minimum": 0,
            "type": "integer"
          },
          "time_window": {
            "type": "string"
          }
        },
        "required": [
          "query",
          "results_count",
          "results"
        ],
        "type": "object"
      },
      "DataApi.StatsResponse": {
        "properties": {
          "active_screens": {
            "minimum": 0,
            "type": "integer"
          },
          "embeddings": {
            "additionalProperties": {},
            "type": "object"
          },
          "metrics_last_24h": {
            "minimum": 0,
            "type": "integer"
          },
          "partner": {
            "type": "string"
          }
        },
        "required": [
          "active_screens",
          "metrics_last_24h",
          "embeddings"
        ],
        "type": "object"
      },
      "DataApi.StreamResponse": {
        "properties": {
          "note": {
            "description": "Server-Sent Events stream. Each event carries `data: <json>` lines per the SSE spec.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "DataApi.UsageResponse": {
        "properties": {
          "data": {
            "items": {
              "additionalProperties": {},
              "type": "object"
            },
            "type": "array"
          },
          "period_days": {
            "minimum": 1,
            "type": "integer"
          }
        },
        "required": [
          "data",
          "period_days"
        ],
        "type": "object"
      },
      "Error": {
        "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": "Link to the documentation for this error code (https://api.trillboards.com/docs/errors#<code>).",
                "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 (when applicable).",
                "type": "string"
              },
              "request_id": {
                "description": "Echoed X-Request-Id for correlation.",
                "pattern": "^req_[a-f0-9]{32}$",
                "type": "string"
              },
              "type": {
                "description": "Stripe-style error taxonomy (derived from utils/errorCatalog).",
                "enum": [
                  "api_error",
                  "authentication_error",
                  "idempotency_error",
                  "invalid_request_error",
                  "permission_error",
                  "rate_limit_error"
                ],
                "type": "string"
              }
            },
            "required": [
              "type",
              "code",
              "message",
              "request_id"
            ],
            "type": "object"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "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"
      },
      "ExampleHealthcheckResponse": {
        "properties": {
          "request_id": {
            "description": "Echoed X-Request-Id for correlation.",
            "example": "req_a1b2c3d4e5f6789012345678901234ab",
            "pattern": "^req_[a-f0-9]{32}$",
            "type": "string"
          },
          "service": {
            "example": "trillboards-api",
            "type": "string"
          },
          "status": {
            "description": "Always \"ok\" when the service is reachable.",
            "enum": [
              "ok"
            ],
            "type": "string"
          },
          "version": {
            "example": "2026.04.15",
            "type": "string"
          }
        },
        "required": [
          "status",
          "service"
        ],
        "type": "object"
      },
      "FeedbackRewardQuery": {
        "additionalProperties": false,
        "properties": {
          "cursor": {
            "description": "Opaque next_cursor from this same date scope; rewind after scan_complete.",
            "maxLength": 8192,
            "minLength": 1,
            "type": "string"
          },
          "from": {
            "description": "Inclusive first play date, YYYY-MM-DD; date range is at most 31 days.",
            "example": "2026-10-14",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "type": "string"
          },
          "idempotency_key": {
            "description": "Unique caller-owned billing key for this page; committed retries return the receipt, not another read.",
            "maxLength": 256,
            "minLength": 1,
            "type": "string"
          },
          "limit": {
            "description": "Maximum native play states returned on this metered page.",
            "maximum": 5000,
            "minimum": 1,
            "type": "integer"
          },
          "to": {
            "description": "Inclusive last play date, YYYY-MM-DD; date range is at most 31 days.",
            "example": "2026-10-14",
            "format": "date",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "type": "string"
          }
        },
        "required": [
          "from",
          "to",
          "idempotency_key"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "description": "The same platform API key as `BearerAuth`, sent as a raw header value instead of a Bearer credential. `X-Trillboards-API-Key` is accepted as a legacy alias for this header. Scope semantics are identical.",
        "in": "header",
        "name": "X-API-Key",
        "type": "apiKey"
      },
      "BearerAuth": {
        "description": "Platform API key sent as `Authorization: Bearer <key>`. This is the canonical carrier. Scopes listed on an operation are enforced by `middleware/apiKeyAuth.js`; ALL listed scopes must be held. A key holding a namespace wildcard (e.g. `sdk:*`) satisfies any scope in that namespace. An operation listing no scope accepts any valid, unexpired key.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "contact": {
      "email": "api@trillboards.com",
      "name": "Trillboards API Support",
      "url": "https://trillboards.com/support"
    },
    "description": "Licensable audience, movement and inventory datasets.\n\nEverything you can call with a **data-licensing key**: aggregate audience and\nmovement data, lookalike modelling, FEIN signals, semantic inventory search,\nand the streaming/usage endpoints that go with them.\n\nAll paths are absolute against `https://api.trillboards.com`.",
    "license": {
      "name": "Proprietary",
      "url": "https://trillboards.com/terms"
    },
    "title": "Trillboards Data API",
    "version": "1.0.0"
  },
  "openapi": "3.0.3",
  "paths": {
    "/v1/data/aggregate": {
      "get": {
        "description": "Groups audience metrics by the requested dimension and returns summary statistics. Choose the dimension with `group_by` and the measure with `metric`; each lists its allowed values on the parameter.",
        "operationId": "dataApiAggregate",
        "parameters": [
          {
            "description": "Dimension to group aggregations by.",
            "in": "query",
            "name": "group_by",
            "required": false,
            "schema": {
              "description": "Dimension to group aggregations by.",
              "enum": [
                "venue_type",
                "daypart",
                "day_of_week",
                "income_estimated_level",
                "primary_mood"
              ],
              "type": "string"
            }
          },
          {
            "description": "Metric to aggregate on.",
            "in": "query",
            "name": "metric",
            "required": false,
            "schema": {
              "description": "Metric to aggregate on.",
              "enum": [
                "avg_face_count",
                "avg_attention",
                "income_confidence",
                "max_face_count"
              ],
              "type": "string"
            }
          },
          {
            "description": "Relative time window (e.g. \"24h\", \"7d\").",
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "description": "Relative time window (e.g. \"24h\", \"7d\").",
              "example": "24h",
              "maxLength": 32,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.AggregateResponse"
                }
              }
            },
            "description": "Grouped aggregations."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:historical"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:historical"
            ]
          }
        ],
        "summary": "Aggregate audience analytics for a time window.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/lookalike": {
      "get": {
        "description": "Returns screens ranked by audience similarity to the source screen. Use this for lookalike targeting expansion.",
        "operationId": "dataApiLookalikeScreens",
        "parameters": [
          {
            "description": "Internal screen identifier.",
            "in": "query",
            "name": "screen_id",
            "required": true,
            "schema": {
              "description": "Internal screen identifier.",
              "example": "screen_abc123",
              "maxLength": 128,
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "min_similarity",
            "required": false,
            "schema": {
              "maximum": 1,
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.LookalikeResponse"
                }
              }
            },
            "description": "Ranked lookalike matches."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:live"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:live"
            ]
          }
        ],
        "summary": "Find screens with similar audience profiles.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/query": {
      "post": {
        "description": "FEIN query DSL. Supply a `dataset`, optional `dimensions`, `measures`, `filters`, and `order_by`. Invalid dimensions or measures return `invalid_dimensions` / `invalid_measures` with the allowed set in `help`. `inference_outcomes` returns camera measurements (`inference_source = device`) unless the request filters on `inference_source`. Every `inference_outcomes` row carries `provenance`, and the response states which sources were read.",
        "operationId": "dataApiQueryDsl",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DataApi.QueryDslBody"
              }
            }
          },
          "description": "FEIN DSL request body.",
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.QueryDslResponse"
                }
              }
            },
            "description": "Rows matching the DSL query."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "410": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:historical"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:historical"
            ]
          }
        ],
        "summary": "Composable query DSL over approved datasets.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/screens": {
      "get": {
        "description": "Paginated list of screens under the caller's API key scope. Returns the current status and venue metadata for each screen.",
        "operationId": "dataApiListScreens",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "maximum": 1000000,
              "minimum": 0,
              "type": "integer"
            }
          },
          {
            "description": "Filter by venue taxonomy type (e.g. bar, restaurant).",
            "in": "query",
            "name": "venue_type",
            "required": false,
            "schema": {
              "description": "Filter by venue taxonomy type (e.g. bar, restaurant).",
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "description": "ISO-3166 alpha-2 country code.",
            "in": "query",
            "name": "country",
            "required": false,
            "schema": {
              "description": "ISO-3166 alpha-2 country code.",
              "example": "US",
              "maxLength": 2,
              "minLength": 2,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.ScreensListResponse"
                }
              }
            },
            "description": "Matching screens."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:live"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:live"
            ]
          }
        ],
        "summary": "List screens visible to the caller.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/screens/{screenId}/audience": {
      "get": {
        "description": "Returns the latest live audience snapshot for the screen. When no snapshot is available the `audience` field is null.",
        "operationId": "dataApiScreenAudienceLive",
        "parameters": [
          {
            "description": "Internal screen identifier.",
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "description": "Internal screen identifier.",
              "example": "screen_abc123",
              "maxLength": 128,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.ScreenAudienceResponse"
                }
              }
            },
            "description": "Latest audience sample (possibly null)."
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:live"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:live"
            ]
          }
        ],
        "summary": "Real-time audience snapshot for a screen.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/screens/{screenId}/history": {
      "get": {
        "description": "Time-range query over the `audience_metrics` table. Defaults to the 100 most recent windows.",
        "operationId": "dataApiScreenAudienceHistory",
        "parameters": [
          {
            "description": "Internal screen identifier.",
            "in": "path",
            "name": "screenId",
            "required": true,
            "schema": {
              "description": "Internal screen identifier.",
              "example": "screen_abc123",
              "maxLength": 128,
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "ISO-8601 start timestamp.",
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "description": "ISO-8601 start timestamp.",
              "example": "2026-04-01T00:00:00.000Z",
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "ISO-8601 end timestamp.",
            "in": "query",
            "name": "until",
            "required": false,
            "schema": {
              "description": "ISO-8601 end timestamp.",
              "example": "2026-04-15T00:00:00.000Z",
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 1000,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.ScreenHistoryResponse"
                }
              }
            },
            "description": "Historical audience metrics."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:historical"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:historical"
            ]
          }
        ],
        "summary": "Historical audience_metrics rows for a screen.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/semantic-search": {
      "get": {
        "description": "Vector similarity search against recently-ingested audience scene embeddings. Useful for finding screens or moments matching a natural-language prompt.",
        "operationId": "dataApiSemanticSearch",
        "parameters": [
          {
            "in": "query",
            "name": "q",
            "required": true,
            "schema": {
              "maxLength": 512,
              "minLength": 3,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Relative time window (e.g. \"24h\", \"7d\").",
            "in": "query",
            "name": "since",
            "required": false,
            "schema": {
              "description": "Relative time window (e.g. \"24h\", \"7d\").",
              "example": "24h",
              "maxLength": 32,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "min_similarity",
            "required": false,
            "schema": {
              "maximum": 1,
              "minimum": 0,
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.SemanticSearchResponse"
                }
              }
            },
            "description": "Ranked matches."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:semantic"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:semantic"
            ]
          }
        ],
        "summary": "Similarity search over scene embeddings.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/stats": {
      "get": {
        "description": "Returns active screens, embeddings stats, and recent metrics counts for the authenticated partner.",
        "operationId": "dataApiStats",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.StatsResponse"
                }
              }
            },
            "description": "Platform counters."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:live"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:live"
            ]
          }
        ],
        "summary": "Platform-wide counters.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/stream": {
      "get": {
        "description": "Opens an SSE connection delivering live audience + observation events. Filter with `observation_family` or `venue_type` query params.",
        "operationId": "dataApiStream",
        "parameters": [
          {
            "in": "query",
            "name": "observation_family",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "venue_type",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/event-stream": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.StreamResponse"
                }
              }
            },
            "description": "Long-lived text/event-stream response."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "stream:live"
            ]
          },
          {
            "ApiKeyAuth": [
              "stream:live"
            ]
          }
        ],
        "summary": "Server-Sent Events stream of live audience/observation events.",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "metered",
        "x-trillboards-idempotent": false,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/data/usage": {
      "get": {
        "description": "Returns per-day request counts and billable records over the requested window. Used by the metering/billing dashboard.",
        "operationId": "dataApiUsage",
        "parameters": [
          {
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "maximum": 90,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DataApi.UsageResponse"
                }
              }
            },
            "description": "Daily usage rows."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [
          {
            "BearerAuth": [
              "read:live"
            ]
          },
          {
            "ApiKeyAuth": [
              "read:live"
            ]
          }
        ],
        "summary": "API usage for the authenticated key (billing dashboard).",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    },
    "/v1/example/healthcheck": {
      "get": {
        "description": "Proof-of-concept operation wired through the Zod → OpenAPI 3.1 generator. Later migration phases replace this with real Data API operations.",
        "operationId": "exampleHealthcheck",
        "parameters": [
          {
            "description": "When `true`, include diagnostic fields in the response.",
            "in": "query",
            "name": "verbose",
            "required": false,
            "schema": {
              "description": "When `true`, include diagnostic fields in the response.",
              "enum": [
                "true",
                "false"
              ],
              "example": "false",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExampleHealthcheckResponse"
                }
              }
            },
            "description": "Service is healthy."
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        },
        "security": [],
        "summary": "Example healthcheck (Phase 3 pipeline proof)",
        "tags": [
          "Data API"
        ],
        "x-trillboards-agent-safe": true,
        "x-trillboards-cost-tier": "free",
        "x-trillboards-idempotent": true,
        "x-trillboards-llm-example": "Ping this endpoint before any real call to confirm the API is reachable and your network route is healthy.",
        "x-trillboards-source": "data",
        "x-trillboards-surface": "data-api"
      }
    }
  },
  "servers": [
    {
      "description": "Production API",
      "url": "https://api.trillboards.com"
    }
  ],
  "tags": [
    {
      "description": "Licensable third-party datasets: aggregate audience, lookalike modelling, FEIN signals, semantic inventory search, streaming and usage.",
      "name": "Data API"
    }
  ],
  "x-trillboards-bundle": {
    "slug": "data",
    "sources": [
      {
        "file": "generated/data-api.json",
        "key": "data"
      }
    ]
  }
}
