openapi: 3.0.3
info:
  title: COLA Cloud API
  description: |
    Access the United States TTB COLA Registry through COLA Cloud's self-service API.

    ## Overview

    COLA Cloud provides programmatic access to the 2005-forward application database for
    Certificate of Label Approval (COLA) data from the Alcohol and Tobacco Tax and Trade Bureau
    (TTB). Bulk warehouse delivery remains available separately through a data license. The
    self-service API includes:

    - 2005-forward approved alcohol labels
    - Product details, brand information, and approval dates
    - Label images with barcodes and QR codes
    - Permittee (permit holder) information
    - LLM-enriched data including tasting notes and category classification

    ## Authentication

    Self-serve REST, SDK, and CLI requests use a COLA Cloud API key. Include your
    key in the `X-API-Key` header:

    ```
    curl -H "X-API-Key: your_api_key_here" https://app.colacloud.us/api/v1/colas
    ```

    MCP clients may pass the same API key as `Authorization: Bearer <key>`.
    Linked assistant OAuth connections may also call the API with a WorkOS-issued
    Bearer token. OAuth access is scoped per connected app and is in private
    rollout until account linking is self-serve.

    ## Quotas

    Usage is metered by **detail views** (single-record lookups) and **list records** (items returned by list/search endpoints), not raw request count. Quotas are per-user across all channels (web, API, SDKs, CLI, MCP).

    | Tier | Detail Views/mo | List Records/mo | Burst/min |
    |------|----------------|-----------------|-----------|
    | Free | 200 | 10,000 | 10 |
    | Starter | 2,000 | 100,000 | 60 |
    | Pro | 10,000 | 1,000,000 | 120 |

    Quota headers are included in all responses:

    - `X-Detail-Views-Limit`: Your monthly detail view limit
    - `X-Detail-Views-Remaining`: Detail views remaining this month
    - `X-List-Records-Limit`: Your monthly list record limit
    - `X-List-Records-Remaining`: List records remaining this month
    - `X-Quota-Reset`: Unix timestamp when quotas reset

    ## Pagination

    List endpoints support pagination via `page` and `per_page` parameters:

    - `page`: Page number (default: 1)
    - `per_page`: Results per page (default: 20, max: 100)

    Responses include pagination metadata:

    ```json
    {
      "data": [...],
      "pagination": {
        "mode": "offset",
        "page": 1,
        "per_page": 20,
        "total": null,
        "pages": null,
        "has_more": true
      }
    }
    ```

    For deep result sets, use **cursor-based pagination** with the `cursor` parameter
    (returned as `next_cursor` in the response). Cursor pagination performs consistently
    regardless of depth.

  version: 1.0.0
  contact:
    name: COLA Cloud Support
    email: help@colacloud.us
    url: https://colacloud.us
  license:
    name: Proprietary
    url: https://colacloud.us/terms

servers:
  - url: https://app.colacloud.us/api/v1
    description: Production server

security:
  - ApiKeyHeader: []
  - BearerApiKey: []

tags:
  - name: COLAs
    description: Certificate of Label Approval operations
  - name: Permittees
    description: Permit holder operations
  - name: Barcodes
    description: Barcode lookup operations
  - name: Usage
    description: API usage and rate limit information
  - name: Saved Searches
    description: Durable saved-search and scheduled alert operations
  - name: Reference Data
    description: |
      Free reference datasets from the TTB. These endpoints require an API key but do not count against your monthly quota.

paths:
  /colas:
    get:
      tags:
        - COLAs
      summary: Search COLAs
      description: |
        Search and filter Certificates of Label Approval with full-text search and various filters.
        Results are ordered by approval date (descending).

        When no explicit date filters (`approval_date_from`, `approval_date_to`) are provided, results
        default to the last 365 days, except for exact 14-digit TTB-ID searches and non-approved
        status filters (`expired`, `surrendered`, or `revoked`), which search all available history.
        Explicit date filters always take precedence. The `defaults_applied` field in the response
        indicates when the recent-date default is active.
      operationId: listColas
      parameters:
        - name: q
          in: query
          description: Text search query. Searches TTB ID, brand, product, class, permit number, and applicant/company name. Exact 14-digit TTB IDs take precedence; other barcode-shaped queries are treated as barcode lookups first.
          schema:
            type: string
          example: "cabernet sauvignon napa"
        - name: product_type
          in: query
          description: Filter by one or more TTB product types. Comma-separated values are accepted.
          style: form
          explode: false
          schema:
            type: array
            items:
              type: string
              enum: [wine, malt beverage, distilled spirits]
          example: [wine, malt beverage]
        - name: category
          in: query
          description: Filter by one or more consumer-facing taxonomy roots. Comma-separated values are accepted. This trade-oriented classification is independent of the TTB `product_type` field.
          style: form
          explode: false
          schema:
            type: array
            items:
              type: string
              enum: [Beer, Wine, Liquor]
          example: [Beer, Wine]
        - name: derived_subcategory
          in: query
          description: Filter by a canonical taxonomy path prefix, such as "Beer > Ale" or "Wine > Red Wine". Paths with a published transition alias are accepted and resolved to their current canonical path.
          schema:
            type: string
          example: "Beer > Ale"
        - name: origin
          in: query
          description: Filter by origin/country (e.g., "american", "french", "italian")
          schema:
            type: string
          example: american
        - name: domestic_or_imported
          in: query
          description: Filter by domestic or imported origin.
          schema:
            type: string
            enum: [domestic, imported]
          example: domestic
        - name: status
          in: query
          description: Filter by current TTB application status. Non-approved statuses search all available history when no explicit approval-date filter is supplied.
          schema:
            type: string
            enum: [approved, expired, surrendered, revoked]
          example: approved
        - name: brand_name
          in: query
          description: Filter by brand name (partial match, case-insensitive)
          schema:
            type: string
          example: "Opus One"
        - name: permit_number
          in: query
          description: Filter by permit number (exact match, e.g. "CA-I-12345")
          schema:
            type: string
          example: "CA-I-12345"
        - name: barcode_value
          in: query
          description: Filter by the COLA record's main barcode value (exact match). Numeric values may include spaces or hyphens.
          schema:
            type: string
          example: "012345678905"
        - name: approval_date_from
          in: query
          description: Filter by minimum approval date (YYYY-MM-DD)
          schema:
            type: string
            format: date
          example: "2024-01-01"
        - name: approval_date_to
          in: query
          description: Filter by maximum approval date (YYYY-MM-DD)
          schema:
            type: string
            format: date
          example: "2024-12-31"
        - name: abv_min
          in: query
          description: Filter by minimum alcohol by volume (ABV) percentage
          schema:
            type: number
            format: float
          example: 12.0
        - name: abv_max
          in: query
          description: Filter by maximum alcohol by volume (ABV) percentage
          schema:
            type: number
            format: float
          example: 15.0
        - name: volume_unit
          in: query
          description: Filter by package volume unit. Required when volume_min or volume_max is provided.
          schema:
            type: string
            enum: [beer barrels, fluid ounces, gallons, liters, milliliters, pints, quarts]
          example: milliliters
        - name: volume_min
          in: query
          description: Filter by minimum package volume in the selected volume_unit.
          schema:
            type: number
            format: float
            minimum: 0
          example: 375
        - name: volume_max
          in: query
          description: Filter by maximum package volume in the selected volume_unit.
          schema:
            type: number
            format: float
            minimum: 0
          example: 750
        - name: container_type
          in: query
          description: Filter by one or more derived container types. Comma-separated values are accepted.
          style: form
          explode: false
          schema:
            type: array
            items:
              type: string
              enum: [bag, bottle, box, can, carton, cask, jug, keg, pod, pouch]
          example: [bottle, can]
        - name: page
          in: query
          description: Page number (max 100)
          schema:
            type: integer
            default: 1
            minimum: 1
            maximum: 100
        - name: per_page
          in: query
          description: Results per page (max 100)
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
      responses:
        '200':
          description: Successful response
          headers:
            X-Detail-Views-Limit:
              $ref: '#/components/headers/X-Detail-Views-Limit'
            X-Detail-Views-Remaining:
              $ref: '#/components/headers/X-Detail-Views-Remaining'
            X-List-Records-Limit:
              $ref: '#/components/headers/X-List-Records-Limit'
            X-List-Records-Remaining:
              $ref: '#/components/headers/X-List-Records-Remaining'
            X-Quota-Reset:
              $ref: '#/components/headers/X-Quota-Reset'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ColaSummary'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
              example:
                data:
                  - ttb_id: "24001234567"
                    brand_name: "Sample Winery"
                    product_name: "Reserve Cabernet Sauvignon"
                    product_type: "wine"
                    class_name: "table red wine"
                    origin_name: "american"
                    permit_number: "CA-I-12345"
                    application_status: approved
                    approval_date: "2024-01-20"
                    image_count: 2
                    has_barcode: true
                pagination:
                  mode: "offset"
                  page: 1
                  per_page: 20
                  total: null
                  pages: null
                  has_more: true
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /colas/{ttb_id}:
    get:
      tags:
        - COLAs
      summary: Get COLA details
      description: |
        Retrieve detailed information for a single COLA by its TTB ID.
        Includes all label images with short-lived CloudFront URLs and barcode data.
        Counts as one detail view against your quota.
      operationId: getCola
      parameters:
        - name: ttb_id
          in: path
          required: true
          description: The unique TTB identifier for the COLA
          schema:
            type: string
          example: "24001234567"
      responses:
        '200':
          description: Successful response
          headers:
            X-Detail-Views-Limit:
              $ref: '#/components/headers/X-Detail-Views-Limit'
            X-Detail-Views-Remaining:
              $ref: '#/components/headers/X-Detail-Views-Remaining'
            X-List-Records-Limit:
              $ref: '#/components/headers/X-List-Records-Limit'
            X-List-Records-Remaining:
              $ref: '#/components/headers/X-List-Records-Remaining'
            X-Quota-Reset:
              $ref: '#/components/headers/X-Quota-Reset'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/ColaDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /permittees:
    get:
      tags:
        - Permittees
      summary: Search permittees
      description: |
        Search permit holders (wineries, breweries, distilleries, importers, etc.).
        Results are ordered by total COLA count (most active first).
      operationId: listPermittees
      parameters:
        - name: q
          in: query
          description: Search by company name (partial match)
          schema:
            type: string
          example: "Gallo"
        - name: state
          in: query
          description: Filter by state (two-letter code, case-insensitive)
          schema:
            type: string
          example: "CA"
        - name: permittee_type
          in: query
          description: Filter by permittee type (partial match, case-insensitive)
          schema:
            type: string
          example: "Importer"
        - name: is_active
          in: query
          description: Filter by active permit status
          schema:
            type: string
            enum: ["true", "false"]
        - name: page
          in: query
          description: Page number
          schema:
            type: integer
            default: 1
            minimum: 1
        - name: per_page
          in: query
          description: Results per page (max 100)
          schema:
            type: integer
            default: 20
            minimum: 1
            maximum: 100
      responses:
        '200':
          description: Successful response
          headers:
            X-Detail-Views-Limit:
              $ref: '#/components/headers/X-Detail-Views-Limit'
            X-Detail-Views-Remaining:
              $ref: '#/components/headers/X-Detail-Views-Remaining'
            X-List-Records-Limit:
              $ref: '#/components/headers/X-List-Records-Limit'
            X-List-Records-Remaining:
              $ref: '#/components/headers/X-List-Records-Remaining'
            X-Quota-Reset:
              $ref: '#/components/headers/X-Quota-Reset'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/PermitteeSummary'
                  pagination:
                    $ref: '#/components/schemas/Pagination'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /permittees/{permit_number}:
    get:
      tags:
        - Permittees
      summary: Get permittee details
      description: |
        Retrieve detailed information for a single permittee by permit number.
        Includes the 10 most recent COLAs for this permit holder.
        Counts as one detail view against your quota.
      operationId: getPermittee
      parameters:
        - name: permit_number
          in: path
          required: true
          description: The permit number (e.g., "CA-I-12345")
          schema:
            type: string
          example: "CA-I-12345"
      responses:
        '200':
          description: Successful response
          headers:
            X-Detail-Views-Limit:
              $ref: '#/components/headers/X-Detail-Views-Limit'
            X-Detail-Views-Remaining:
              $ref: '#/components/headers/X-Detail-Views-Remaining'
            X-List-Records-Limit:
              $ref: '#/components/headers/X-List-Records-Limit'
            X-List-Records-Remaining:
              $ref: '#/components/headers/X-List-Records-Remaining'
            X-Quota-Reset:
              $ref: '#/components/headers/X-Quota-Reset'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/PermitteeDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /barcode/{barcode_value}:
    get:
      tags:
        - Barcodes
      summary: Lookup by barcode
      description: |
        Find COLAs by barcode value (UPC, EAN, etc.).
        Returns up to 100 COLAs associated with the given barcode.
      operationId: lookupBarcode
      parameters:
        - name: barcode_value
          in: path
          required: true
          description: The barcode value (UPC-A, EAN-13, etc.)
          schema:
            type: string
          example: "012345678905"
      responses:
        '200':
          description: Successful response
          headers:
            X-Detail-Views-Limit:
              $ref: '#/components/headers/X-Detail-Views-Limit'
            X-Detail-Views-Remaining:
              $ref: '#/components/headers/X-Detail-Views-Remaining'
            X-List-Records-Limit:
              $ref: '#/components/headers/X-List-Records-Limit'
            X-List-Records-Remaining:
              $ref: '#/components/headers/X-List-Records-Remaining'
            X-Quota-Reset:
              $ref: '#/components/headers/X-Quota-Reset'
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/BarcodeResult'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /usage:
    get:
      tags:
        - Usage
      summary: Get API usage
      description: |
        Get current usage statistics for your account.
        Shows detail view and list record consumption, limits, and tier information.
      operationId: getUsage
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/UsageStats'
              example:
                data:
                  tier: "free"
                  current_period: "2024-01"
                  detail_views:
                    used: 42
                    limit: 200
                    remaining: 158
                  list_records:
                    used: 150
                    limit: 500
                    remaining: 350
                  per_minute_limit: 10
        '401':
          $ref: '#/components/responses/Unauthorized'

  /saved-searches:
    post:
      tags:
        - Saved Searches
      summary: Create a saved search
      description: |
        Create a durable saved search using the non-date filter field names from the
        web app's SearchForm. Include a schedule object to enable a recurring
        saved-search alert.

        OAuth callers must have the `saved_searches:write` scope. Approval-date
        filters are not saved-search configuration; scheduled runs use the saved
        search's previous run and current run as the approval-date window. Broad
        recurring searches without a meaningful filter are rejected. Each user can
        have up to 10 active scheduled watchlists.
      operationId: createSavedSearch
      security:
        - ApiKeyHeader: []
        - BearerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SavedSearchCreateRequest'
            example:
              name: "Weekly California cabernet approvals"
              filters:
                query: "cabernet"
                type_wine: true
              schedule:
                cadence: "weekly"
                day: 0
                active: true
      responses:
        '201':
          description: Saved search created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/SavedSearch'
        '400':
          description: Invalid schedule or filters
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Missing scope, feature disabled, or scheduled watchlist limit reached
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /processing-times:
    get:
      tags:
        - Reference Data
      summary: Get COLA processing times
      description: |
        Current TTB label application processing times by commodity (Wine, Malt Beverage, Distilled Spirits).
        Sourced from the TTB's public processing times page.

        This endpoint is free — it does not consume quota.
      operationId: getProcessingTimes
      parameters:
        - name: commodity
          in: query
          description: Filter by commodity (case-insensitive, e.g. "wine")
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ProcessingTime'
                  meta:
                    $ref: '#/components/schemas/MetaTotal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /processing-times/formula:
    get:
      tags:
        - Reference Data
      summary: Get formula processing times
      description: |
        Current TTB formula application processing times by formula type and commodity.
        Sourced from the TTB's public processing times page.

        This endpoint is free — it does not consume quota.
      operationId: getFormulaProcessingTimes
      parameters:
        - name: formula_type
          in: query
          description: Filter by formula type
          schema:
            type: string
        - name: commodity
          in: query
          description: Filter by commodity (case-insensitive)
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/FormulaProcessingTime'
                  meta:
                    $ref: '#/components/schemas/MetaTotal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /processing-times/registration:
    get:
      tags:
        - Reference Data
      summary: Get registration processing times
      description: |
        Current TTB original permit application processing times by category and application type.
        Sourced from the TTB's public processing times page.

        This endpoint is free — it does not consume quota.
      operationId: getRegistrationProcessingTimes
      parameters:
        - name: category
          in: query
          description: Filter by category
          schema:
            type: string
        - name: application_type
          in: query
          description: Filter by application type
          schema:
            type: string
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/RegistrationProcessingTime'
                  meta:
                    $ref: '#/components/schemas/MetaTotal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /production-reports:
    get:
      tags:
        - Reference Data
      summary: Get production reports
      description: |
        TTB statistical production reports — monthly and annual data on alcohol production,
        removals, and inventories by commodity and statistical group.

        This endpoint is free — it does not consume quota.
      operationId: getProductionReports
      parameters:
        - name: commodity
          in: query
          description: Filter by commodity
          schema:
            type: string
        - name: year
          in: query
          description: Filter by year
          schema:
            type: integer
        - name: month
          in: query
          description: Filter by month (1-12)
          schema:
            type: integer
            minimum: 1
            maximum: 12
        - name: report_type
          in: query
          description: Filter by report type
          schema:
            type: string
        - name: statistical_group
          in: query
          description: Filter by statistical group
          schema:
            type: string
        - name: page
          in: query
          description: Page number (max 100)
          schema:
            type: integer
            default: 1
            minimum: 1
            maximum: 100
        - name: per_page
          in: query
          description: Results per page (max 100)
          schema:
            type: integer
            default: 100
            minimum: 1
            maximum: 100
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/ProductionReport'
                  meta:
                    $ref: '#/components/schemas/MetaPaginated'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /avas:
    get:
      tags:
        - Reference Data
      summary: List American Viticultural Areas
      description: |
        List all American Viticultural Areas (AVAs) — federally designated wine grape-growing regions.
        Optionally filter by state or search by name.

        This endpoint is free — it does not consume quota.
      operationId: listAvas
      parameters:
        - name: state
          in: query
          description: Filter by state (matches within the states array)
          schema:
            type: string
          example: "CA"
        - name: q
          in: query
          description: Search by AVA name (partial match, case-insensitive)
          schema:
            type: string
          example: "napa"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/AvaSummary'
                  meta:
                    $ref: '#/components/schemas/MetaTotal'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

  /avas/{ava_id}:
    get:
      tags:
        - Reference Data
      summary: Get AVA details
      description: |
        Retrieve detailed information for a single American Viticultural Area by its AVA ID.
        Includes parent/child AVA relationships and regulatory references.

        This endpoint is free — it does not consume quota.
      operationId: getAva
      parameters:
        - name: ava_id
          in: path
          required: true
          description: The unique AVA identifier
          schema:
            type: string
          example: "napa-valley"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/AvaDetail'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
        '429':
          $ref: '#/components/responses/RateLimitExceeded'

components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key passed in the X-API-Key header
    BearerApiKey:
      type: http
      scheme: bearer
      description: |
        API key passed as `Authorization: Bearer <key>`, or a linked WorkOS
        OAuth access token passed as `Authorization: Bearer <token>`. API keys
        are equivalent to the X-API-Key scheme. OAuth tokens must include the
        scope required by the endpoint.

  headers:
    X-Detail-Views-Limit:
      description: Your monthly detail view limit
      schema:
        type: integer
      example: 200
    X-Detail-Views-Remaining:
      description: Detail views remaining this month
      schema:
        type: integer
      example: 158
    X-List-Records-Limit:
      description: Your monthly list record limit
      schema:
        type: integer
      example: 500
    X-List-Records-Remaining:
      description: List records remaining this month
      schema:
        type: integer
      example: 350
    X-Quota-Reset:
      description: Unix timestamp when quotas reset (first of next month)
      schema:
        type: integer
      example: 1706745600

  schemas:
    Pagination:
      type: object
      properties:
        mode:
          type: string
          enum: [offset, cursor]
          description: Pagination mode
        page:
          type: integer
          description: Current page number (offset mode only)
        per_page:
          type: integer
          description: Results per page
        total:
          type: integer
          nullable: true
          description: Total number of results (null in offset mode)
        pages:
          type: integer
          nullable: true
          description: Total number of pages (null in offset mode)
        has_more:
          type: boolean
          description: Whether more results are available
        next_cursor:
          type: string
          nullable: true
          description: Cursor for next page (cursor mode only)

    SavedSearchCreateRequest:
      type: object
      required: [filters]
      properties:
        name:
          type: string
          description: Human-readable saved search name
          example: "Weekly California cabernet approvals"
        filters:
          $ref: '#/components/schemas/SavedSearchFilters'
        schedule:
          $ref: '#/components/schemas/SavedSearchSchedule'

    SavedSearchFilters:
      type: object
      description: |
        Filters using non-date web SearchForm field names. Approval-date filters are
        intentionally not part of saved-search configuration; active recurring
        schedules apply the date window for each run. ABV bounds only count as
        meaningful when they narrow the range (`abv_min` greater than 0 or `abv_max`
        less than 100).
      additionalProperties: false
      properties:
        query:
          type: string
          nullable: true
        brand_name:
          type: string
          nullable: true
        barcode_value:
          type: string
          nullable: true
          description: Exact filter against the COLA record's main barcode value.
        application_status:
          type: string
          nullable: true
          enum: [approved, expired, surrendered, revoked]
          description: Current TTB application status.
        type_malt_beverage:
          type: boolean
        type_wine:
          type: boolean
        type_distilled_spirit:
          type: boolean
        derived_beer:
          type: boolean
        derived_wine:
          type: boolean
        derived_liquor:
          type: boolean
        derived_category:
          type: string
          nullable: true
        origin_domestic:
          type: boolean
        origin_imported:
          type: boolean
        country:
          type: string
          nullable: true
        us_state:
          type: string
          nullable: true
        permit_number_text:
          type: string
          nullable: true
        applicant_us_state:
          type: string
          nullable: true
        abv_min:
          type: number
          format: float
          nullable: true
          description: Meaningful for active recurring schedules only when greater than 0.
        abv_max:
          type: number
          format: float
          nullable: true
          description: Meaningful for active recurring schedules only when less than 100.
        volume_unit:
          type: string
          nullable: true
          enum: [beer barrels, fluid ounces, gallons, liters, milliliters, pints, quarts]
          description: Required when volume_min or volume_max is set.
        volume_min:
          type: number
          format: float
          nullable: true
        volume_max:
          type: number
          format: float
          nullable: true
        container_bottle:
          type: boolean
        container_can:
          type: boolean
        container_keg:
          type: boolean
        container_box:
          type: boolean
        container_cask:
          type: boolean
        container_pouch:
          type: boolean
        container_pod:
          type: boolean
        container_carton:
          type: boolean
        container_bag:
          type: boolean
        container_jug:
          type: boolean

    SavedSearchSchedule:
      type: object
      properties:
        active:
          type: boolean
          default: true
        cadence:
          type: string
          nullable: true
          enum: [daily, weekly, monthly]
        day:
          type: integer
          nullable: true
          description: Weekly uses 0-6 where 0 is Monday; monthly uses 1-28.

    SavedSearch:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
        name:
          type: string
        filters:
          $ref: '#/components/schemas/SavedSearchFilters'
        schedule:
          $ref: '#/components/schemas/SavedSearchSchedule'
        created_at:
          type: string
          format: date-time
          nullable: true
        manage_url:
          type: string
          format: uri
        dashboard_url:
          type: string
          format: uri

    ColaSummary:
      type: object
      description: Summary view of a COLA (used in list responses)
      properties:
        ttb_id:
          type: string
          description: Unique TTB identifier
        brand_name:
          type: string
          description: Brand name on the label
        product_name:
          type: string
          description: Product name
        product_type:
          type: string
          enum: [wine, malt beverage, distilled spirits]
          description: Type of alcohol product
        class_name:
          type: string
          description: TTB classification (e.g., "table red wine")
        origin_name:
          type: string
          description: Country/region of origin
        permit_number:
          type: string
          description: Permit number of the producer/importer
        application_status:
          type: string
          enum: [approved, expired, surrendered, revoked]
          nullable: true
          description: Current application status published by TTB
        approval_date:
          type: string
          format: date
          nullable: true
        image_count:
          type: integer
          description: Number of label images
        has_barcode:
          type: boolean
          description: Whether any barcode was extracted from label images

    ColaDetail:
      type: object
      description: Detailed view of a COLA (includes all fields, images, and barcodes)
      allOf:
        - $ref: '#/components/schemas/ColaSummary'
        - type: object
          properties:
            application_type:
              type: string
              nullable: true
              description: TTB application type
            application_date:
              type: string
              format: date
              nullable: true
            expiration_date:
              type: string
              format: date
              nullable: true
            class_id:
              type: string
              description: TTB class ID code
            origin_id:
              type: string
              description: TTB origin ID code
            latest_update_date:
              type: string
              format: date
              nullable: true
            is_distinctive_container:
              type: boolean
            for_distinctive_capacity:
              type: string
              nullable: true
              description: Volume of the distinctive container as free text
            is_resubmission:
              type: boolean
            for_resubmission_ttb_id:
              type: string
              nullable: true
            for_exemption_state:
              type: string
              nullable: true
            address_recipient:
              type: string
              nullable: true
              description: Label recipient name
            address_zip_code:
              type: string
              nullable: true
            address_state:
              type: string
              nullable: true
            grape_varietals:
              type: array
              items:
                type: string
              nullable: true
              description: Grape varieties (wine only)
            wine_vintage_year:
              type: integer
              nullable: true
              description: Vintage year (wine only)
            wine_appellation:
              type: string
              nullable: true
              description: Wine appellation (wine only)
            llm_container_type:
              type: string
              nullable: true
              description: LLM-identified container type (bottle, can, etc.)
            llm_product_description:
              type: string
              nullable: true
              description: LLM-generated product description
            llm_brand_established_year:
              type: integer
              nullable: true
            llm_category:
              type: string
              nullable: true
              description: Display name of the deepest published taxonomy node for this record. This can be a non-leaf category when a more specific classification is not defensible.
              example: Bourbon
            llm_category_path:
              type: string
              nullable: true
              description: Canonical consumer-facing taxonomy path rooted at Beer, Wine, or Liquor. This classification is independent of the TTB product_type field.
              example: "Liquor > Whiskey > American Whiskey > Bourbon"
            llm_tasting_note_flavors:
              type: array
              items:
                type: string
              nullable: true
              description: LLM-extracted tasting notes
            llm_artwork_credit:
              type: string
              nullable: true
            llm_wine_designation:
              type: string
              nullable: true
            llm_beer_ibu:
              type: string
              nullable: true
              description: IBU rating (beer only)
            llm_beer_hops_varieties:
              type: array
              items:
                type: string
              nullable: true
            llm_liquor_aged_years:
              type: integer
              nullable: true
            llm_liquor_finishing_process:
              type: string
              nullable: true
            llm_liquor_grains:
              type: array
              items:
                type: string
              nullable: true
            barcode_type:
              type: string
              nullable: true
              description: Primary barcode type (upca, ean13, etc.)
            barcode_value:
              type: string
              nullable: true
              description: Primary barcode value
            qrcode_url:
              type: string
              nullable: true
              description: URL encoded in QR code (if present)
            has_front_image:
              type: boolean
            has_back_image:
              type: boolean
            has_neck_image:
              type: boolean
            has_strip_image:
              type: boolean
            images:
              type: array
              items:
                $ref: '#/components/schemas/ColaImage'
            barcodes:
              type: array
              items:
                $ref: '#/components/schemas/Barcode'

    ColaImage:
      type: object
      properties:
        ttb_image_id:
          type: string
          description: Unique image identifier
        image_index:
          type: integer
          description: Image index within the COLA
        container_position:
          type: string
          enum: [front, back, neck, strip, other]
          description: Position on the container
        extension_type:
          type: string
          description: File extension (jpg, png, etc.)
        width_pixels:
          type: integer
        height_pixels:
          type: integer
        width_inches:
          type: number
          format: float
          nullable: true
        height_inches:
          type: number
          format: float
          nullable: true
        file_size_mb:
          type: number
          format: float
        barcode_count:
          type: integer
        qrcode_count:
          type: integer
        image_url:
          type: string
          format: uri
          description: Short-lived CloudFront URL for the image

    Barcode:
      type: object
      properties:
        barcode_type:
          type: string
          description: Barcode format (upca, ean13, qr, etc.)
        barcode_value:
          type: string
          description: Decoded barcode value
        ttb_image_id:
          type: string
          description: Image this barcode was found in
        width_pixels:
          type: integer
        height_pixels:
          type: integer
        orientation:
          type: string
          enum: [horizontal, vertical, square]
        relative_image_position:
          type: string
          description: Position in the image (e.g., "bottom center")

    PermitteeSummary:
      type: object
      properties:
        permit_number:
          type: string
          description: TTB permit number
        company_name:
          type: string
        company_state:
          type: string
          description: Two-letter state code
        company_zip_code:
          type: string
        permittee_type:
          type: string
          description: Type of permit (e.g., "Bonded Winery", "Importer")
        is_active:
          type: boolean
        active_reason:
          type: string
          nullable: true
        colas:
          type: integer
          description: Total number of COLAs for this permittee.
        colas_approved:
          type: integer
          description: Number of approved COLAs.
        last_cola_application_date:
          type: string
          format: date
          nullable: true
          description: Date of most recent COLA application.

    PermitteeDetail:
      type: object
      allOf:
        - $ref: '#/components/schemas/PermitteeSummary'
        - type: object
          properties:
            recent_colas:
              type: array
              items:
                $ref: '#/components/schemas/ColaSummary'
              description: 10 most recent COLAs for this permittee

    BarcodeResult:
      type: object
      properties:
        barcode_value:
          type: string
          description: The barcode value that was searched
        barcode_type:
          type: string
          nullable: true
          description: The barcode format (upca, ean13, etc.)
        colas:
          type: array
          items:
            $ref: '#/components/schemas/ColaSummary'
          description: COLAs associated with this barcode
        total_colas:
          type: integer
          description: Number of COLAs found

    UsageQuota:
      type: object
      description: Usage quota for a single meter
      properties:
        used:
          type: integer
          description: Amount used this period
        limit:
          type: integer
          description: Maximum allowed this period
        remaining:
          type: integer
          description: Amount remaining this period

    UsageStats:
      type: object
      properties:
        tier:
          type: string
          enum: [free, starter, pro]
          description: Your account tier
        current_period:
          type: string
          description: Current billing period (YYYY-MM)
        detail_views:
          $ref: '#/components/schemas/UsageQuota'
        list_records:
          $ref: '#/components/schemas/UsageQuota'
        per_minute_limit:
          type: integer
          description: Maximum requests per minute (burst limit)

    MetaTotal:
      type: object
      description: Response metadata with total count
      properties:
        total:
          type: integer
          description: Total number of results

    MetaPaginated:
      type: object
      description: Response metadata with pagination
      properties:
        total:
          type: integer
          description: Total number of results
        page:
          type: integer
          description: Current page number
        per_page:
          type: integer
          description: Results per page
        has_more:
          type: boolean
          description: Whether more results are available

    ProcessingTime:
      type: object
      description: COLA label application processing time for a commodity
      properties:
        commodity:
          type: string
          description: Commodity name (e.g., "Wine", "Malt Beverage", "Distilled Spirits")
        snapshot_date:
          type: string
          format: date
          description: Date the processing time was captured
        turnaround_days_median:
          type: integer
          nullable: true
          description: Median turnaround time in days
        queue_frontier_date:
          type: string
          format: date
          nullable: true
          description: Date of the oldest application currently in the queue
        queue_depth_days:
          type: integer
          nullable: true
          description: Number of days of backlog in the queue

    FormulaProcessingTime:
      type: object
      description: Formula application processing time by formula type and commodity
      properties:
        commodity:
          type: string
          nullable: true
          description: Commodity name, if applicable
        formula_type:
          type: string
          description: Type of formula application
        snapshot_date:
          type: string
          format: date
          description: Date the processing time was captured
        turnaround_days_median:
          type: integer
          nullable: true
          description: Median turnaround time in days
        queue_frontier_date:
          type: string
          format: date
          nullable: true
          description: Date of the oldest application currently in the queue
        queue_depth_days:
          type: integer
          nullable: true
          description: Number of days of backlog in the queue

    RegistrationProcessingTime:
      type: object
      description: Original permit application processing time by category and application type
      properties:
        category:
          type: string
          description: Permit category
        application_type:
          type: string
          description: Type of permit application
        report_month:
          type: string
          format: date
          description: Report month (YYYY-MM-DD, first of month)
        turnaround_days_median:
          type: integer
          nullable: true
          description: Median turnaround time in days
        snapshot_date:
          type: string
          format: date
          description: Date the processing time was captured

    ProductionReport:
      type: object
      description: TTB statistical production report record
      properties:
        commodity:
          type: string
          description: Commodity name
        report_type:
          type: string
          description: Type of report (e.g., monthly, annual)
        year:
          type: integer
          description: Report year
        month_number:
          type: integer
          nullable: true
          description: Report month (1-12), null for annual reports
        report_date:
          type: string
          format: date
          description: Report date
        statistical_group:
          type: string
          description: Statistical grouping
        statistical_category:
          type: string
          description: Statistical category within the group
        statistical_detail:
          type: string
          description: Specific statistical detail
        count_ims:
          type: integer
          nullable: true
          description: Count of items (where applicable)
        value:
          type: number
          format: float
          nullable: true
          description: Reported value
        value_unit:
          type: string
          description: Unit of the reported value
        is_redacted:
          type: boolean
          description: Whether the value was redacted by the TTB

    AvaSummary:
      type: object
      description: Summary view of an American Viticultural Area
      properties:
        ava_id:
          type: string
          description: Unique AVA identifier
        name:
          type: string
          description: Official AVA name
        aka:
          type: string
          nullable: true
          description: Alternative name, if any
        states:
          type: array
          items:
            type: string
          nullable: true
          description: US states the AVA spans
        counties:
          type: array
          items:
            type: string
          nullable: true
          description: Counties the AVA spans
        established_date:
          type: string
          format: date
          nullable: true
          description: Date the AVA was established
        removed_date:
          type: string
          format: date
          nullable: true
          description: Date the AVA was removed, if applicable

    AvaDetail:
      type: object
      description: Detailed view of an American Viticultural Area
      allOf:
        - $ref: '#/components/schemas/AvaSummary'
        - type: object
          properties:
            valid_start_date:
              type: string
              format: date
              nullable: true
              description: Start date of the current AVA boundary definition
            valid_end_date:
              type: string
              format: date
              nullable: true
              description: End date of the current AVA boundary definition
            parent_avas:
              type: array
              items:
                type: string
              nullable: true
              description: Parent AVA identifiers
            child_avas:
              type: array
              items:
                type: string
              nullable: true
              description: Child AVA identifiers
            petitioner:
              type: string
              nullable: true
              description: Entity that petitioned for the AVA
            cfr_author:
              type: string
              nullable: true
              description: Author of the CFR entry
            cfr_index:
              type: string
              nullable: true
              description: CFR index reference

    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: Machine-readable error code
            message:
              type: string
              description: Human-readable error message
            details:
              type: object
              additionalProperties: true
              description: Additional error metadata
          required: [code, message, details]
      required: [error]

  responses:
    Unauthorized:
      description: Missing or invalid API key or Bearer token
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missing_key:
              summary: Missing API key or Bearer token
              value:
                error:
                  code: "unauthorized"
                  message: "API key required. Provide via X-API-Key header or Authorization: Bearer."
                  details: {}
            invalid_key:
              summary: Invalid API key
              value:
                error:
                  code: "unauthorized"
                  message: "Invalid API key."
                  details: {}
            revoked_key:
              summary: Revoked API key
              value:
                error:
                  code: "unauthorized"
                  message: "This API key has been revoked."
                  details: {}
            oauth_connection_required:
              summary: OAuth connection is not linked
              value:
                error:
                  code: "oauth_connection_required"
                  message: "No COLA Cloud account is linked to this OAuth connection."
                  details: {}

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: "not_found"
              message: "COLA 12345678901 not found"
              details: {}

    RateLimitExceeded:
      description: Quota or burst limit exceeded
      headers:
        X-Detail-Views-Limit:
          $ref: '#/components/headers/X-Detail-Views-Limit'
        X-Detail-Views-Remaining:
          $ref: '#/components/headers/X-Detail-Views-Remaining'
        X-List-Records-Limit:
          $ref: '#/components/headers/X-List-Records-Limit'
        X-List-Records-Remaining:
          $ref: '#/components/headers/X-List-Records-Remaining'
        X-Quota-Reset:
          $ref: '#/components/headers/X-Quota-Reset'
        Retry-After:
          description: Seconds to wait before retrying (for per-minute burst limits)
          schema:
            type: integer
          example: 60
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            detail_view_quota:
              summary: Detail view quota exceeded
              value:
                error:
                  code: "quota_exceeded"
                  message: "Detail view quota exceeded. Upgrade your plan for more detail views."
                  details:
                    limit: 200
                    used: 200
                    upgrade_url: "https://app.colacloud.us/dashboard/subscriptions"
            list_record_quota:
              summary: List record quota exceeded
              value:
                error:
                  code: "quota_exceeded"
                  message: "List record quota exceeded. Upgrade your plan for more list records."
                  details:
                    limit: 500
                    used: 500
                    upgrade_url: "https://app.colacloud.us/dashboard/subscriptions"
            minute_limit:
              summary: Per-minute burst limit exceeded
              value:
                error:
                  code: "rate_limit_exceeded"
                  message: "Too many requests. Please wait before making more requests."
                  details:
                    limit_per_minute: 10
                    retry_after: 60
