openapi: 3.1.0
info:
  title: AffiBuild Trends API
  version: 1.0.0
  description: Read-only product catalog API for AffiBuild Trends. Data is curated and may not be real-time; always inspect meta.freshness.
servers:
  - url: https://affibuild.com/api/trends/v1
security:
  - bearerAuth: []
paths:
  /products:
    get:
      operationId: listProducts
      summary: List products
      parameters:
        - { name: page, in: query, schema: { type: integer, minimum: 1, default: 1 } }
        - { name: per_page, in: query, schema: { type: integer, minimum: 1, maximum: 48, default: 24 } }
        - { name: q, in: query, schema: { type: string } }
        - { name: category, in: query, schema: { type: string } }
        - { name: sort, in: query, schema: { type: string, example: opportunity } }
        - { name: min_price, in: query, schema: { type: number } }
        - { name: max_price, in: query, schema: { type: number } }
        - { name: min_score, in: query, schema: { type: number } }
        - { name: min_rating, in: query, schema: { type: number } }
        - { name: min_sales, in: query, schema: { type: integer } }
        - { name: mall_only, in: query, schema: { type: boolean } }
      responses:
        '200':
          description: Product list
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProductListResponse' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }
  /products/{id}:
    get:
      operationId: getProduct
      summary: Get one product
      parameters:
        - { name: id, in: path, required: true, schema: { type: string } }
      responses:
        '200':
          description: Product detail
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ProductResponse' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '404': { description: Product not found }
  /categories:
    get:
      operationId: listCategories
      summary: List product categories
      responses:
        '200':
          description: Category list
          content:
            application/json:
              schema: { $ref: '#/components/schemas/CollectionResponse' }
        '401': { $ref: '#/components/responses/Unauthorized' }
  /status:
    get:
      operationId: getStatus
      summary: Get API and catalog freshness
      responses:
        '200':
          description: Service status
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
                properties:
                  meta: { $ref: '#/components/schemas/Meta' }
                  requestId: { type: string }
        '401': { $ref: '#/components/responses/Unauthorized' }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: AffiBuildTrendsApiKey
  schemas:
    Meta:
      type: object
      additionalProperties: true
      properties:
        freshness: { type: object, additionalProperties: true }
        score_version: { type: string }
    Product:
      type: object
      description: Product fields may grow; clients should ignore unknown fields.
      additionalProperties: true
      properties:
        id: { type: string }
        product_key: { type: string, description: Stable shopid:itemid identifier as a string. }
        name: { type: string }
        price: { type: number }
        rating: { type: number, nullable: true }
        historical_sales: { type: integer, nullable: true, description: Accumulated sales, not monthly sales. }
        monthly_sales: { type: integer, nullable: true }
        product_url: { type: string, format: uri }
        images: { type: array, items: { type: string, format: uri } }
        score: { type: number, nullable: true }
    ProductListResponse:
      type: object
      required: [items, meta]
      properties:
        items: { type: array, items: { $ref: '#/components/schemas/Product' } }
        pagination: { type: object, additionalProperties: true }
        meta: { $ref: '#/components/schemas/Meta' }
        requestId: { type: string }
    ProductResponse:
      type: object
      properties:
        product: { $ref: '#/components/schemas/Product' }
        meta: { $ref: '#/components/schemas/Meta' }
        requestId: { type: string }
    CollectionResponse:
      type: object
      additionalProperties: true
      properties:
        items: { type: array, items: { type: object, additionalProperties: true } }
        meta: { $ref: '#/components/schemas/Meta' }
        requestId: { type: string }
    Error:
      type: object
      properties:
        error: { type: string }
        requestId: { type: string }
  responses:
    Unauthorized:
      description: Invalid, expired, or revoked API key
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: Rate limit, concurrency, or quota exceeded
      headers:
        Retry-After: { schema: { type: integer } }
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
