openapi: "3.1.0"
info:
  title: Drop Beacon API
  description: >
    Drop Beacon (edc4me.com) tracks 150,000+ everyday carry (EDC) products from
    1,100+ brands, with live prices across retailers. This read-only API provides
    product drops, brand listings, and product search. Categories include knives,
    fidget toys/sliders/spinners, pens, wallets, flashlights, multi-tools, and
    patches. No key is needed; please keep request rates modest (heavy automated
    traffic is rate-limited). Product pages on edc4me.com are the authoritative,
    live source for prices.
  version: "1.0.0"
  contact:
    email: direct@grade5materials.com
  license:
    name: All rights reserved
servers:
  - url: https://edc4me.com
    description: Production

paths:
  /api/drops:
    get:
      operationId: listDrops
      summary: List recent product drops
      description: >
        Returns EDC product drops. By default returns currently available products
        ordered by newest first. Supports filtering by brand, category, and sort order.
      parameters:
        - name: q
          in: query
          description: Full-text search query (searches product titles)
          schema:
            type: string
        - name: brand
          in: query
          description: Filter by brand slug (e.g. "spyderco", "benchmade")
          schema:
            type: string
        - name: category
          in: query
          description: Filter by product category
          schema:
            type: string
            enum: [knife, fidget, pen, wallet, flashlight, multi_tool, patch, other]
        - name: sort
          in: query
          description: Sort order
          schema:
            type: string
            enum: [newest, price_asc, price_desc]
            default: newest
        - name: sold_out
          in: query
          description: If true, returns sold-out products from the past 7 days instead of available products
          schema:
            type: boolean
            default: false
        - name: limit
          in: query
          description: Number of results (max 100)
          schema:
            type: integer
            default: 50
            maximum: 100
        - name: offset
          in: query
          description: Pagination offset
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: List of product drops
          content:
            application/json:
              schema:
                type: object
                properties:
                  drops:
                    type: array
                    items:
                      $ref: "#/components/schemas/Drop"
                  total:
                    type: integer
                    description: Total matching results

  /api/search:
    get:
      operationId: search
      summary: Search brands and products
      description: >
        Full-text search across brand names and product titles.
        Returns up to 5 brand matches and 8 product matches.
      parameters:
        - name: q
          in: query
          required: true
          description: Search query (minimum 2 characters)
          schema:
            type: string
      responses:
        "200":
          description: Search results
          content:
            application/json:
              schema:
                type: object
                properties:
                  brands:
                    type: array
                    items:
                      $ref: "#/components/schemas/BrandSummary"
                  products:
                    type: array
                    items:
                      $ref: "#/components/schemas/ProductSummary"

  /api/brands:
    get:
      operationId: listBrands
      summary: List EDC brands
      description: >
        Returns all active brands tracked by Drop Beacon.
        Includes brand metadata, category, and live product counts.
      parameters:
        - name: category
          in: query
          description: Filter by brand category
          schema:
            type: string
            enum: [knife, fidget, pen, wallet, flashlight, multi_tool, patch, other]
        - name: tab
          in: query
          description: "featured: curated priority brands; popular: sorted by follower + share count"
          schema:
            type: string
            enum: [featured, popular]
      responses:
        "200":
          description: List of brands
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Brand"

  /api/brands/{id}/products:
    get:
      operationId: getBrandProducts
      summary: Get products for a specific brand
      parameters:
        - name: id
          in: path
          required: true
          description: Brand ID (integer)
          schema:
            type: integer
      responses:
        "200":
          description: Brand products
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/Drop"

components:
  schemas:
    Drop:
      type: object
      properties:
        id:
          type: integer
          description: Product ID
        title:
          type: string
          description: Product name
        slug:
          type: string
          description: URL-safe product identifier (used in /product/[slug])
        price:
          type: string
          description: Current price in USD (decimal string)
        compareAtPrice:
          type: string
          nullable: true
          description: Original/compare-at price if on sale
        isAvailable:
          type: boolean
          description: Whether the product is currently in stock
        category:
          type: string
          nullable: true
          description: Product category (knife, fidget, pen, etc.)
        productUrl:
          type: string
          nullable: true
          description: Direct URL to buy the product (may include affiliate parameters)
        firstSeenAt:
          type: string
          format: date-time
          description: When Drop Beacon first detected this product
        brand:
          $ref: "#/components/schemas/BrandSummary"
        images:
          type: array
          items:
            type: object
            properties:
              src:
                type: string
              alt:
                type: string
                nullable: true

    Brand:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
          description: URL-safe brand identifier (used in /brands/[slug])
        websiteUrl:
          type: string
          description: Brand's official website
        category:
          type: string
          description: Primary product category
        logoUrl:
          type: string
          nullable: true
        isRetailer:
          type: boolean
          description: True if this is a multi-brand retailer rather than a single brand
        productCount:
          type: integer
          description: Number of currently available products

    BrandSummary:
      type: object
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
        category:
          type: string
          nullable: true
        logoUrl:
          type: string
          nullable: true

    ProductSummary:
      type: object
      properties:
        id:
          type: integer
        title:
          type: string
        slug:
          type: string
          nullable: true
        price:
          type: string
          nullable: true
        isAvailable:
          type: boolean
        productUrl:
          type: string
          nullable: true
        brand:
          $ref: "#/components/schemas/BrandSummary"
        images:
          type: array
          items:
            type: object
            properties:
              src:
                type: string
              alt:
                type: string
                nullable: true
