{
  "openapi": "3.0.3",
  "info": {
    "title": "NightBar Content API",
    "version": "1.1.0",
    "description": "NightBar 新闻、节日与社交情绪内容的只读 API。基础状态、分类和近期节日接口公开，搜索、历史简报、文章详情、节日列表与节日详情需要 API Key。API Key 用于结构化调用治理、限流和统计，不代表公开网站内容保密。"
  },
  "servers": [
    {
      "url": "https://nightbar.cn",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Discovery",
      "description": "API 发现与数据集状态"
    },
    {
      "name": "News",
      "description": "简报、文章和搜索"
    },
    {
      "name": "Festivals",
      "description": "节日日历、节日介绍和社交情绪"
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "tags": ["Discovery"],
        "summary": "Discover API endpoints",
        "operationId": "getApiRoot",
        "responses": {
          "200": {
            "description": "API endpoint directory",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiRootResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/latest": {
      "get": {
        "tags": ["News"],
        "summary": "Get the latest daily brief",
        "operationId": "getLatestBrief",
        "responses": {
          "200": {
            "description": "Latest NightBar brief",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BriefResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/briefs/{date}": {
      "get": {
        "tags": ["News"],
        "summary": "Get a daily brief by date",
        "operationId": "getBriefByDate",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "description": "Brief date in YYYY-MM-DD format",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "example": "2026-07-19"
          }
        ],
        "responses": {
          "200": {
            "description": "Requested NightBar brief",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BriefResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/articles/{articleId}": {
      "get": {
        "tags": ["News"],
        "summary": "Get one article by its stable public id",
        "operationId": "getArticle",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "articleId",
            "in": "path",
            "required": true,
            "description": "Stable id in nb-YYYYMMDD-NNN format",
            "schema": {
              "type": "string",
              "pattern": "^nb-[0-9]{8}-[0-9]+$"
            },
            "example": "nb-20260719-001"
          }
        ],
        "responses": {
          "200": {
            "description": "Requested article",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArticleResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "tags": ["News"],
        "summary": "Search NightBar articles",
        "description": "Searches titles, summaries, NightBar analysis text, sources and topics, with optional category, topic and brief-date filters.",
        "operationId": "searchArticles",
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Keyword or phrase, up to 100 characters",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "example": "精酿啤酒"
          },
          {
            "name": "category",
            "in": "query",
            "description": "NightBar category id",
            "schema": {
              "$ref": "#/components/schemas/CategoryId"
            }
          },
          {
            "name": "topic",
            "in": "query",
            "description": "Exact topic tag, up to 50 characters",
            "schema": {
              "type": "string",
              "maxLength": 50
            },
            "example": "精酿啤酒"
          },
          {
            "name": "from",
            "in": "query",
            "description": "Earliest brief date, inclusive",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Latest brief date, inclusive",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Results per page",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque cursor returned by a previous search response",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked article summaries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    },
    "/api/v1/categories": {
      "get": {
        "tags": ["Discovery"],
        "summary": "List supported categories",
        "operationId": "getCategories",
        "responses": {
          "200": {
            "description": "Category ids, display names and article counts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CategoriesResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/festivals": {
      "get": {
        "tags": ["Festivals"],
        "summary": "List festival summaries",
        "description": "Returns compact festival records suitable for calendar and directory views. Full social-emotion sections are available from the detail endpoint.",
        "operationId": "listFestivals",
        "security": [
          { "ApiKeyAuth": [] },
          { "BearerAuth": [] }
        ],
        "parameters": [
          { "$ref": "#/components/parameters/FestivalFrom" },
          { "$ref": "#/components/parameters/FestivalTo" },
          { "$ref": "#/components/parameters/FestivalYear" },
          { "$ref": "#/components/parameters/FestivalMonth" },
          { "$ref": "#/components/parameters/FestivalCategory" },
          {
            "name": "contentStatus",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["historical", "published"]
            }
          },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "Paginated festival summaries",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FestivalListResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/festivals/upcoming": {
      "get": {
        "tags": ["Festivals"],
        "summary": "List upcoming festivals",
        "description": "Public convenience endpoint for the next calendar window, using Singapore/China time when from is omitted.",
        "operationId": "getUpcomingFestivals",
        "parameters": [
          { "$ref": "#/components/parameters/FestivalFrom" },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 366,
              "default": 30
            }
          },
          { "$ref": "#/components/parameters/FestivalCategory" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "Upcoming festival summaries",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FestivalListResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/festivals/search": {
      "get": {
        "tags": ["Festivals"],
        "summary": "Search festivals and social-emotion content",
        "operationId": "searchFestivals",
        "security": [
          { "ApiKeyAuth": [] },
          { "BearerAuth": [] }
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "example": "社恐"
          },
          { "$ref": "#/components/parameters/FestivalFrom" },
          { "$ref": "#/components/parameters/FestivalTo" },
          { "$ref": "#/components/parameters/FestivalYear" },
          { "$ref": "#/components/parameters/FestivalMonth" },
          { "$ref": "#/components/parameters/FestivalCategory" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Cursor" }
        ],
        "responses": {
          "200": {
            "description": "Ranked festival summaries",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FestivalListResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/festivals/categories": {
      "get": {
        "tags": ["Festivals"],
        "summary": "List festival categories",
        "operationId": "getFestivalCategories",
        "responses": {
          "200": {
            "description": "Festival category ids, names and counts",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FestivalCategoriesResponse" }
              }
            }
          },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/festivals/{festivalId}": {
      "get": {
        "tags": ["Festivals"],
        "summary": "Get one festival with its full social-emotion sections",
        "operationId": "getFestival",
        "security": [
          { "ApiKeyAuth": [] },
          { "BearerAuth": [] }
        ],
        "parameters": [
          {
            "name": "festivalId",
            "in": "path",
            "required": true,
            "description": "Stable public id in nf-YYYYMMDD-xxxxxxxxxx format",
            "schema": {
              "type": "string",
              "pattern": "^nf-[0-9]{8}-[a-f0-9]{10}$"
            },
            "example": "nf-20260706-c0a54b8c7d"
          }
        ],
        "responses": {
          "200": {
            "description": "Festival detail",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FestivalDetailResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/Unavailable" }
        }
      }
    },
    "/api/v1/meta": {
      "get": {
        "tags": ["Discovery"],
        "summary": "Get dataset metadata",
        "operationId": "getMetadata",
        "responses": {
          "200": {
            "description": "Dataset counts, version and date range",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MetaResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "tags": ["Discovery"],
        "summary": "Check API and dataset availability",
        "operationId": "getHealth",
        "responses": {
          "200": {
            "description": "API is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Unavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "NightBar client API key"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "The same NightBar API key supplied as a Bearer token"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid request parameter",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Requested resource was not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid NightBar API key",
        "headers": {
          "WWW-Authenticate": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-client request limit exceeded",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Limit": {
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            }
          },
          "RateLimit-Reset": {
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Unavailable": {
        "description": "News data is temporarily unavailable",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      }
    },
    "parameters": {
      "FestivalFrom": {
        "name": "from",
        "in": "query",
        "description": "Earliest festival date, inclusive",
        "schema": { "type": "string", "format": "date" }
      },
      "FestivalTo": {
        "name": "to",
        "in": "query",
        "description": "Latest festival date, inclusive",
        "schema": { "type": "string", "format": "date" }
      },
      "FestivalYear": {
        "name": "year",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1900, "maximum": 2100 }
      },
      "FestivalMonth": {
        "name": "month",
        "in": "query",
        "description": "Calendar month; year is required when month is supplied",
        "schema": { "type": "integer", "minimum": 1, "maximum": 12 }
      },
      "FestivalCategory": {
        "name": "category",
        "in": "query",
        "description": "Festival category id or exact display name",
        "schema": { "type": "string", "maxLength": 80 }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Results per page",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Opaque cursor returned by a previous response",
        "schema": { "type": "string" }
      }
    },
    "schemas": {
      "CategoryId": {
        "type": "string",
        "enum": [
          "industry",
          "capital",
          "policy",
          "insight",
          "strategy",
          "pricing",
          "tech",
          "trend",
          "international"
        ]
      },
      "Category": {
        "type": "object",
        "required": ["id", "name"],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/CategoryId"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "Source": {
        "type": "object",
        "required": ["name", "urls"],
        "properties": {
          "name": {
            "type": "string"
          },
          "urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri"
            }
          }
        }
      },
      "ArticleSummary": {
        "type": "object",
        "required": [
          "id",
          "legacyId",
          "briefDate",
          "publishedAt",
          "publishedText",
          "title",
          "summary",
          "category",
          "topics",
          "source",
          "canonicalUrl"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "nb-20260719-001"
          },
          "legacyId": {
            "type": "integer"
          },
          "briefDate": {
            "type": "string",
            "format": "date"
          },
          "publishedAt": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "publishedText": {
            "type": "string",
            "nullable": true,
            "description": "Original publication-date label retained for historical records; publishedAt is the strict machine-readable date."
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "category": {
            "$ref": "#/components/schemas/Category"
          },
          "topics": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "source": {
            "$ref": "#/components/schemas/Source"
          },
          "canonicalUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Article": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ArticleSummary"
          },
          {
            "type": "object",
            "required": ["analysis"],
            "properties": {
              "analysis": {
                "type": "string",
                "description": "NightBar analysis converted from presentation HTML to plain text"
              }
            }
          }
        ]
      },
      "Brief": {
        "type": "object",
        "required": [
          "date",
          "displayDate",
          "issue",
          "overview",
          "articleCount",
          "canonicalUrl",
          "articles"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "displayDate": {
            "type": "string"
          },
          "issue": {
            "type": "integer",
            "nullable": true
          },
          "overview": {
            "type": "string"
          },
          "articleCount": {
            "type": "integer"
          },
          "canonicalUrl": {
            "type": "string",
            "format": "uri"
          },
          "articles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Article"
            }
          }
        }
      },
      "ApiRootResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": {
            "type": "string",
            "example": "v1"
          },
          "data": {
            "type": "object"
          },
          "meta": {
            "type": "object"
          }
        }
      },
      "BriefResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": {
            "type": "string",
            "example": "v1"
          },
          "data": {
            "$ref": "#/components/schemas/Brief"
          },
          "meta": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "ArticleResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": {
            "type": "string",
            "example": "v1"
          },
          "data": {
            "$ref": "#/components/schemas/Article"
          },
          "meta": {
            "type": "object"
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": {
            "type": "string",
            "example": "v1"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ArticleSummary"
            }
          },
          "meta": {
            "type": "object",
            "required": ["query", "filters", "page", "datasetUpdatedAt"],
            "properties": {
              "query": {
                "type": "string",
                "nullable": true
              },
              "filters": {
                "type": "object"
              },
              "page": {
                "type": "object",
                "required": ["count", "total", "limit", "nextCursor"],
                "properties": {
                  "count": {
                    "type": "integer"
                  },
                  "total": {
                    "type": "integer"
                  },
                  "limit": {
                    "type": "integer"
                  },
                  "nextCursor": {
                    "type": "string",
                    "nullable": true
                  }
                }
              },
              "datasetUpdatedAt": {
                "type": "string",
                "nullable": true
              },
              "datasetVersion": {
                "type": "string",
                "nullable": true
              }
            }
          }
        }
      },
      "CategoriesResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": {
            "type": "string"
          },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "name", "articleCount"],
              "properties": {
                "id": {
                  "$ref": "#/components/schemas/CategoryId"
                },
                "name": {
                  "type": "string"
                },
                "articleCount": {
                  "type": "integer"
                }
              }
            }
          },
          "meta": {
            "type": "object"
          }
        }
      },
      "FestivalCategory": {
        "type": "object",
        "required": ["id", "name"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" }
        }
      },
      "FestivalSummary": {
        "type": "object",
        "required": ["id", "name", "date", "categories", "introduction", "emotionKeywords", "summary", "contentStatus", "dataAsOf", "canonicalUrl"],
        "properties": {
          "id": { "type": "string", "pattern": "^nf-[0-9]{8}-[a-f0-9]{10}$" },
          "name": { "type": "string" },
          "date": { "type": "string", "format": "date" },
          "categories": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/FestivalCategory" }
          },
          "introduction": { "type": "string" },
          "emotionKeywords": {
            "type": "array",
            "items": { "type": "string" }
          },
          "summary": { "type": "string" },
          "contentStatus": { "type": "string", "enum": ["historical", "published"] },
          "dataAsOf": { "type": "string", "format": "date-time", "nullable": true },
          "canonicalUrl": { "type": "string", "format": "uri" }
        }
      },
      "FestivalSocialEmotion": {
        "type": "object",
        "required": ["coreEmotions", "painPoints", "hotMemes", "userSubtext"],
        "properties": {
          "coreEmotions": { "type": "string" },
          "painPoints": { "type": "string" },
          "hotMemes": { "type": "string" },
          "userSubtext": { "type": "string" }
        }
      },
      "FestivalReference": {
        "type": "object",
        "required": ["title", "url"],
        "properties": {
          "title": { "type": "string" },
          "url": { "type": "string", "format": "uri" }
        }
      },
      "FestivalDetail": {
        "allOf": [
          { "$ref": "#/components/schemas/FestivalSummary" },
          {
            "type": "object",
            "required": ["socialEmotion", "references"],
            "properties": {
              "socialEmotion": { "$ref": "#/components/schemas/FestivalSocialEmotion" },
              "references": {
                "type": "array",
                "items": { "$ref": "#/components/schemas/FestivalReference" }
              }
            }
          }
        ]
      },
      "FestivalListResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": { "type": "string", "example": "v1" },
          "data": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/FestivalSummary" }
          },
          "meta": {
            "type": "object",
            "required": ["filters", "page", "datasetUpdatedAt", "datasetVersion"],
            "additionalProperties": true,
            "properties": {
              "filters": { "type": "object" },
              "page": {
                "type": "object",
                "required": ["count", "total", "limit", "nextCursor"],
                "properties": {
                  "count": { "type": "integer" },
                  "total": { "type": "integer" },
                  "limit": { "type": "integer" },
                  "nextCursor": { "type": "string", "nullable": true }
                }
              },
              "datasetUpdatedAt": { "type": "string", "nullable": true },
              "datasetVersion": { "type": "string", "nullable": true }
            }
          }
        }
      },
      "FestivalDetailResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": { "type": "string" },
          "data": { "$ref": "#/components/schemas/FestivalDetail" },
          "meta": { "type": "object" }
        }
      },
      "FestivalCategoriesResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": { "type": "string" },
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["id", "name", "festivalCount"],
              "properties": {
                "id": { "type": "string" },
                "name": { "type": "string" },
                "festivalCount": { "type": "integer" }
              }
            }
          },
          "meta": { "type": "object" }
        }
      },
      "MetaResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "required": ["datasetVersion", "lastUpdatedAt", "totalBriefs", "totalArticles", "dateRange"],
            "properties": {
              "datasetVersion": {
                "type": "string",
                "nullable": true
              },
              "lastUpdatedAt": {
                "type": "string",
                "nullable": true
              },
              "totalBriefs": {
                "type": "integer"
              },
              "totalArticles": {
                "type": "integer"
              },
              "dateRange": {
                "type": "object",
                "required": ["earliest", "latest"],
                "properties": {
                  "earliest": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  },
                  "latest": {
                    "type": "string",
                    "format": "date",
                    "nullable": true
                  }
                }
              }
            }
          },
          "meta": {
            "type": "object"
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "required": ["apiVersion", "data", "meta"],
        "properties": {
          "apiVersion": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "required": ["status", "service", "datasetUpdatedAt"],
            "properties": {
              "status": {
                "type": "string",
                "enum": ["ok"]
              },
              "service": {
                "type": "string"
              },
              "datasetUpdatedAt": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "meta": {
            "type": "object"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["apiVersion", "error"],
        "properties": {
          "apiVersion": {
            "type": "string",
            "example": "v1"
          },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      }
    }
  }
}
