openapi: 3.0.3
info:
  title: Skillsmith API
  description: |
    API for discovering, searching, and managing Claude Code skills.

    ## Authentication
    Most endpoints are reachable without a key, but unauthenticated callers get only 10 requests
    total, ever, per source IP (a one-time allowance, not a recurring quota) before requiring
    authentication. An API key raises this to a per-minute, tier-based limit. Rate limiting is
    applied per IP (unauthenticated) or per API key (authenticated).

    ## Rate Limits
    - Unauthenticated: 10 requests total (one-time allowance per source IP), then 401
    - Authenticated, per minute (by tier): Community 30, Individual 200, Team 300, Enterprise 600
    - Telemetry endpoint (`/events`, no auth required): 200 requests/minute
    - A separate monthly request quota also applies once authenticated — see
      https://skillsmith.app/docs/api#rate-limiting

    Rate limit headers are included in all responses:
    - `X-RateLimit-Limit`: Maximum requests allowed
    - `X-RateLimit-Remaining`: Remaining requests in window
    - `X-RateLimit-Reset`: Unix timestamp when limit resets
  version: 1.0.0
  contact:
    name: Skillsmith Support
    url: https://github.com/smith-horn/skillsmith
  license:
    name: Elastic License 2.0
    url: https://www.elastic.co/licensing/elastic-license

servers:
  - url: https://api.skillsmith.app
    description: Production (Vercel proxy)
  - url: http://localhost:54321/functions/v1
    description: Local development (Supabase)

tags:
  - name: Skills
    description: Skill discovery and retrieval
  - name: Recommendations
    description: AI-powered skill recommendations
  - name: Telemetry
    description: Anonymous usage analytics
  - name: Ops
    description: Operational endpoints (health, platform statistics)
  - name: Marketing
    description: Early-access signups and contact form submissions
  - name: Billing
    description: Stripe checkout and webhook handling
  - name: Auth
    description: RFC 8628 device authorization flow

paths:
  /skills-search:
    get:
      tags:
        - Skills
      summary: Search for skills
      description: |
        Search for skills using full-text search with optional filters.
        Results are ranked by relevance and quality score.
      operationId: searchSkills
      parameters:
        - name: query
          in: query
          required: false
          description: >-
            Search query. Either `query` OR at least one filter (`category`,
            `trust_tier`, `min_score`) must be provided. When `query` is given it
            must be at least 3 characters (SMI-1613 anti-scraping).
          schema:
            type: string
            minLength: 3
            example: 'testing'
        - name: category
          in: query
          required: false
          description: Filter by category name
          schema:
            type: string
            enum:
              - Development
              - Testing
              - DevOps
              - Documentation
              - Productivity
              - Security
            example: 'Testing'
        - name: trust_tier
          in: query
          required: false
          description: Filter by trust level
          schema:
            type: string
            enum:
              - official
              - verified
              - curated
              - community
              - unverified
            example: 'verified'
        - name: min_score
          in: query
          required: false
          description: Minimum quality score (0-100)
          schema:
            type: integer
            minimum: 0
            maximum: 100
            example: 70
        - name: limit
          in: query
          required: false
          description: Maximum results to return
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
        - name: offset
          in: query
          required: false
          description: Pagination offset
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: Successful search
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalError'

  /skills-get:
    get:
      tags:
        - Skills
      summary: Get skill by ID
      description: |
        Retrieve detailed information about a specific skill.
        Supports ID lookup, author/name format, and fuzzy name matching.
      operationId: getSkill
      parameters:
        - name: id
          in: query
          required: true
          description: |
            Skill identifier. Accepts:
            - UUID: Direct skill ID
            - author/name: Format like "anthropic/commit"
            - name: Fuzzy match by skill name
          schema:
            type: string
            example: 'anthropic/commit'
      responses:
        '200':
          description: Skill found
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SkillResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/NotFound'
        '500':
          $ref: '#/components/responses/InternalError'

  /skills-recommend:
    post:
      tags:
        - Recommendations
      summary: Get skill recommendations
      description: |
        Get AI-powered skill recommendations based on your technology stack.
        Returns skills ranked by relevance to your project.
      operationId: recommendSkills
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RecommendRequest'
      responses:
        '200':
          description: Recommendations generated
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecommendResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalError'

  /events:
    post:
      tags:
        - Telemetry
      summary: Record telemetry event
      description: |
        Record anonymous telemetry events for analytics.
        No personally identifiable information is collected.

        **Privacy**: Events are aggregated and anonymous_id should be
        a client-generated hash, not any user identifier.
      operationId: recordEvent
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TelemetryEvent'
      responses:
        '200':
          description: Event recorded
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                    example: true
        '400':
          $ref: '#/components/responses/BadRequest'

  /health:
    get:
      tags:
        - Ops
      summary: API health check
      description: |
        Returns the health status of the Skillsmith API, including database
        connectivity, skill-data availability, and search-function readiness.
        Returns HTTP 503 when the overall status is `unhealthy`.
      operationId: getHealth
      parameters:
        - name: verbose
          in: query
          required: false
          description: When `true`, include per-check `message` and `details`.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Service is healthy or degraded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'
        '503':
          description: Service is unhealthy or misconfigured
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/HealthResponse'

  /stats:
    get:
      tags:
        - Ops
      summary: Get platform statistics
      description: |
        Returns live platform statistics. The basic response is cached for
        5 minutes. Pass `detailed=true` for a diagnostic dashboard with skill
        breakdowns, quality distribution, recent indexer/refresh runs, and
        72-hour net growth.
      operationId: getStats
      parameters:
        - name: detailed
          in: query
          required: false
          description: When `true`, return the extended diagnostic dashboard.
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Statistics retrieved
          headers:
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatsResponse'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '500':
          $ref: '#/components/responses/InternalError'

  /early-access-signup:
    post:
      tags:
        - Marketing
      summary: Register for early access
      description: |
        Records an early-access email signup and sends a confirmation email
        via Resend. Rate-limited to 5 signups per IP per hour. Includes a
        honeypot `website` field — when populated the request silently
        succeeds (bot detection).
      operationId: earlyAccessSignup
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EarlyAccessSignupRequest'
      responses:
        '200':
          description: Signup recorded (new or already-on-list)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EarlyAccessSignupResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /contact-submit:
    post:
      tags:
        - Marketing
      summary: Submit a contact form
      description: |
        Stores a contact-form submission and sends an email notification to
        the support inbox via Resend.
      operationId: contactSubmit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ContactSubmitRequest'
      responses:
        '200':
          description: Submission accepted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ContactSubmitResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '500':
          $ref: '#/components/responses/InternalError'

  /checkout:
    post:
      tags:
        - Billing
      summary: Create a Stripe Checkout session
      description: |
        Creates a Stripe Checkout session for a paid subscription tier. Returns
        the hosted Checkout URL and session ID. Re-uses an existing open session
        for the same email/tier/period within a 30-minute window (SMI-3162).
      operationId: createCheckout
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CheckoutRequest'
      responses:
        '200':
          description: Checkout session created (or reused)
          headers:
            X-Request-ID:
              $ref: '#/components/headers/X-Request-ID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CheckoutResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'

  /stripe-webhook:
    post:
      tags:
        - Billing
      summary: Receive Stripe webhook events
      description: |
        Stripe webhook receiver. Requests are authenticated by verifying the
        `Stripe-Signature` header against the configured webhook secret — there
        is no API-key or JWT auth. Handles `checkout.session.completed`,
        `customer.subscription.*`, and `invoice.payment_*` events.
      operationId: stripeWebhook
      parameters:
        - name: Stripe-Signature
          in: header
          required: true
          description: Stripe webhook signature for payload verification.
          schema:
            type: string
      requestBody:
        required: true
        description: Raw Stripe event payload (read as text for signature verification).
        content:
          application/json:
            schema:
              type: object
              description: Stripe Event object.
      responses:
        '200':
          description: Event received and processed
          content:
            application/json:
              schema:
                type: object
                properties:
                  received:
                    type: boolean
                    example: true
        '400':
          description: Missing or invalid Stripe signature
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '500':
          description: Webhook configuration error or processing failure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'

  /auth-device-code:
    post:
      tags:
        - Auth
      summary: Request a device authorization code
      description: |
        RFC 8628 §3.1 device authorization request. Issues a `device_code` and
        human-readable `user_code` for the device-login flow. Optional
        `client_meta` is captured for approver device recognition.
      operationId: authDeviceCode
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceCodeRequest'
      responses:
        '200':
          description: Device code issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceCodeResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '500':
          $ref: '#/components/responses/InternalError'

  /auth-device-token:
    post:
      tags:
        - Auth
      summary: Poll for a device access token
      description: |
        RFC 8628 §3.4–§3.5 device access-token (polling) endpoint. Exchanges an
        approved `device_code` for a Supabase Auth session. Uses RFC 8628 status
        codes: 428 `authorization_pending`, 429 `slow_down`, 400 `expired_token`
        / `authorization_declined`.
      operationId: authDeviceToken
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DeviceTokenRequest'
      responses:
        '200':
          description: Authorization approved — session minted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DeviceTokenResponse'
        '400':
          description: Token expired or authorization declined
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '428':
          description: Authorization still pending
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Polling too fast (slow down)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
        '500':
          $ref: '#/components/responses/InternalError'

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'Personal API key (sk_live_*); raises rate limits and unlocks tier-gated features'

  schemas:
    Skill:
      type: object
      required:
        - id
        - name
        - trust_tier
        - created_at
        - updated_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique skill identifier
          example: '550e8400-e29b-41d4-a716-446655440000'
        name:
          type: string
          description: Skill name
          example: 'jest-helper'
        description:
          type: string
          nullable: true
          description: Skill description
          example: 'Helper utilities for Jest testing'
        author:
          type: string
          nullable: true
          description: Skill author
          example: 'community'
        repo_url:
          type: string
          format: uri
          nullable: true
          description: Source repository URL
          example: 'https://github.com/example/jest-helper'
        quality_score:
          type: number
          format: float
          nullable: true
          minimum: 0
          maximum: 1
          description: Quality score (0-1 scale)
          example: 0.85
        trust_tier:
          type: string
          enum:
            - official
            - verified
            - curated
            - community
            - unverified
          description: Trust level
          example: 'community'
        tags:
          type: array
          items:
            type: string
          description: Tags for categorization
          example: ['testing', 'jest', 'javascript']
        stars:
          type: integer
          nullable: true
          description: GitHub stars count
          example: 150
        created_at:
          type: string
          format: date-time
          description: Creation timestamp
        updated_at:
          type: string
          format: date-time
          description: Last update timestamp
        categories:
          type: array
          items:
            type: string
          description: Associated category names
          example: ['Testing', 'Development']

    SearchResult:
      allOf:
        - $ref: '#/components/schemas/Skill'
        - type: object
          properties:
            rank:
              type: number
              format: float
              description: Search relevance rank

    RecommendedSkill:
      allOf:
        - $ref: '#/components/schemas/Skill'
        - type: object
          properties:
            relevance_score:
              type: number
              format: float
              description: Relevance score for the given stack

    SearchResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/SearchResult'
        meta:
          type: object
          properties:
            query:
              type: string
            total:
              type: integer
            limit:
              type: integer
            offset:
              type: integer
            filters:
              type: object
              properties:
                category:
                  type: string
                  nullable: true
                trust_tier:
                  type: string
                  nullable: true
                min_score:
                  type: integer
                  nullable: true

    SkillResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/Skill'

    RecommendRequest:
      type: object
      required:
        - stack
      properties:
        stack:
          type: array
          items:
            type: string
          minItems: 1
          maxItems: 10
          description: Technology stack (e.g., ["react", "typescript"])
          example: ['typescript', 'react', 'jest']
        project_type:
          type: string
          enum:
            - web
            - api
            - cli
            - mobile
            - data
            - ml
          description: Type of project
          example: 'web'
        limit:
          type: integer
          minimum: 1
          maximum: 50
          default: 10
          description: Maximum recommendations

    RecommendResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RecommendedSkill'
        meta:
          type: object
          properties:
            stack:
              type: array
              items:
                type: string
            project_type:
              type: string
              nullable: true
            total:
              type: integer
            limit:
              type: integer

    TelemetryEvent:
      type: object
      required:
        - event
        - anonymous_id
      properties:
        event:
          type: string
          enum:
            - skill_view
            - skill_install
            - skill_uninstall
            - skill_rate
            - search
            - recommend
            - compare
            - validate
          description: Event type
          example: 'search'
        skill_id:
          type: string
          description: Associated skill ID (optional)
          example: '550e8400-e29b-41d4-a716-446655440000'
        anonymous_id:
          type: string
          pattern: '^[a-f0-9-]{16,128}$'
          description: Anonymous client identifier (hex string)
          example: 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4'
        metadata:
          type: object
          description: Optional event metadata
          properties:
            query:
              type: string
            results_count:
              type: integer
            duration_ms:
              type: integer
            version:
              type: string
            platform:
              type: string

    HealthCheck:
      type: object
      properties:
        name:
          type: string
          description: Check name (e.g. database, skills_data, search)
          example: 'database'
        status:
          type: string
          enum:
            - healthy
            - degraded
            - unhealthy
          description: Per-check status
        latencyMs:
          type: integer
          description: Check duration in milliseconds
          example: 42
        message:
          type: string
          description: Human-readable check detail (verbose only)
        details:
          type: object
          additionalProperties: true
          description: Additional check metadata (verbose only)

    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum:
            - healthy
            - degraded
            - unhealthy
          description: Overall service status
        version:
          type: string
          example: '1.0.0'
        timestamp:
          type: string
          format: date-time
        uptime:
          type: integer
          description: Seconds since the function instance started
          example: 1234
        checks:
          type: array
          items:
            $ref: '#/components/schemas/HealthCheck'
        summary:
          type: object
          properties:
            total:
              type: integer
            healthy:
              type: integer
            degraded:
              type: integer
            unhealthy:
              type: integer

    StatsResponse:
      type: object
      properties:
        data:
          type: object
          properties:
            skillCount:
              type: integer
              description: Total number of published skills
              example: 14000
            githubTotal:
              type: integer
              description: Total SKILL.md files found on GitHub at the last indexer run
              example: 14250
            lastUpdated:
              type: string
              format: date-time
            breakdown:
              type: object
              description: Skill counts by state and trust tier (detailed only)
              properties:
                active:
                  type: integer
                quarantined:
                  type: integer
                byTier:
                  type: object
                  properties:
                    verified:
                      type: integer
                    curated:
                      type: integer
                    community:
                      type: integer
                    experimental:
                      type: integer
            qualityDistribution:
              type: object
              description: Quality-score buckets (detailed only)
              properties:
                high:
                  type: integer
                medium:
                  type: integer
                low:
                  type: integer
            recentIndexerRuns:
              type: array
              description: Last 3 indexer runs (detailed only)
              items:
                type: object
            recentRefreshSummary:
              type: object
              description: Last-24h metadata-refresh summary (detailed only)
            netGrowth72h:
              type: object
              description: Net skill growth over the last 72 hours (detailed only)
        cached:
          type: boolean
          description: Whether the response was served from cache

    EarlyAccessSignupRequest:
      type: object
      required:
        - email
      properties:
        email:
          type: string
          format: email
          maxLength: 320
          description: Signup email address
          example: 'dev@example.com'
        source:
          type: string
          enum:
            - homepage_hero
            - homepage_cta
            - api
            - other
          description: Signup source
        utm_source:
          type: string
          maxLength: 100
        utm_medium:
          type: string
          maxLength: 100
        utm_campaign:
          type: string
          maxLength: 100
        metadata:
          type: object
          additionalProperties: true
          description: Optional sanitized metadata (max 10 keys)
        website:
          type: string
          description: Honeypot field — leave empty (populated requests silently succeed)

    EarlyAccessSignupResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: "You're on the list! Check your email for confirmation."
        isNew:
          type: boolean
          description: Whether this email was newly added (false if already on the list)

    ContactSubmitRequest:
      type: object
      required:
        - name
        - email
        - topic
        - message
      properties:
        name:
          type: string
          example: 'Ada Lovelace'
        email:
          type: string
          format: email
        company:
          type: string
          nullable: true
        topic:
          type: string
          enum:
            - general
            - support
            - sales
            - enterprise
            - partnership
            - feedback
            - bug
            - other
          description: Inquiry topic
        message:
          type: string

    ContactSubmitResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: 'Thank you for your message. We will get back to you soon.'
        id:
          type: string
          description: Submission ID (omitted if the submissions table is unavailable)

    CheckoutRequest:
      type: object
      required:
        - tier
        - period
      properties:
        tier:
          type: string
          enum:
            - individual
            - team
            - enterprise
          description: Paid subscription tier
        period:
          type: string
          enum:
            - monthly
            - annual
          description: Billing period
        seatCount:
          type: integer
          minimum: 1
          maximum: 1000
          default: 1
          description: Number of seats (team/enterprise)
        email:
          type: string
          format: email
          description: Customer email (enables session dedup + idempotency)
        successUrl:
          type: string
          format: uri
          description: Override success redirect URL
        cancelUrl:
          type: string
          format: uri
          description: Override cancel redirect URL

    CheckoutResponse:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: Stripe Checkout session URL
        sessionId:
          type: string
          description: Stripe Checkout session ID
        reused:
          type: boolean
          description: True when an existing open session was reused (SMI-3162)

    DeviceCodeRequest:
      type: object
      required:
        - client_type
      properties:
        client_type:
          type: string
          enum:
            - cli
            - mcp
          description: Requesting client type
        client_meta:
          type: object
          description: Optional client metadata for approver device recognition
          properties:
            cli_version:
              type: string
              maxLength: 32
            node_version:
              type: string
              maxLength: 32
            platform:
              type: string
              maxLength: 16
            arch:
              type: string
              maxLength: 16
            hostname:
              type: string
              maxLength: 128

    DeviceCodeResponse:
      type: object
      properties:
        device_code:
          type: string
          description: Opaque device code used for polling
        user_code:
          type: string
          description: Human-readable code (8 chars, displayed XXXX-XXXX)
          example: 'BCDF-GHJK'
        verification_uri:
          type: string
          format: uri
          description: URL the user visits to approve the device
        expires_in:
          type: integer
          description: Seconds until the device code expires
        interval:
          type: integer
          description: Minimum polling interval in seconds

    DeviceTokenRequest:
      type: object
      required:
        - device_code
      properties:
        device_code:
          type: string
          description: Device code issued by /auth-device-code

    DeviceTokenResponse:
      type: object
      properties:
        access_token:
          type: string
          description: Supabase Auth access token
        refresh_token:
          type: string
          description: Supabase Auth refresh token
        token_type:
          type: string
          example: 'bearer'
        expires_in:
          type: integer
          example: 3600

    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: string
          description: Error message
        details:
          type: object
          description: Additional error details

  headers:
    X-RateLimit-Limit:
      schema:
        type: string
      description: Maximum requests per minute
      example: '100'
    X-RateLimit-Remaining:
      schema:
        type: string
      description: Remaining requests in current window
      example: '99'
    X-RateLimit-Reset:
      schema:
        type: string
      description: Unix timestamp when limit resets
      example: '1704672000'
    X-Request-ID:
      schema:
        type: string
        format: uuid
      description: Request tracking ID
      example: '550e8400-e29b-41d4-a716-446655440000'

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: 'Provide a search query or at least one filter (category, trust_tier, min_score)'
            details:
              parameter: 'query'
              received: 'a'

    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: 'Skill not found'
            details:
              id: 'non-existent-skill'

    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: 'Internal server error'
            details:
              request_id: '550e8400-e29b-41d4-a716-446655440000'

    MethodNotAllowed:
      description: HTTP method not allowed for this endpoint
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: 'Method not allowed'

    TooManyRequests:
      description: Rate limit exceeded
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: 'Too many requests. Please try again later.'

    ServiceUnavailable:
      description: Service temporarily unavailable
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: 'Service temporarily unavailable'
