openapi: 3.1.0
info:
  title: Competiflow API
  version: "1.0"
  description: |
    Competitor monitoring API. Create workspaces, add competitors, read interpreted changes,
    and fetch workspace digests.

    ## Authentication
    ```
    Authorization: Bearer sk_live_your_api_key
    ```
  contact:
    name: Competiflow
    url: https://competiflow.com
  license:
    name: Proprietary

servers:
  - url: https://api.competiflow.com/v1
    description: Production

security:
  - BearerAuth: []

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: API key from Settings → API keys

  headers:
    X-RateLimit-Limit:
      schema: { type: integer }
    X-RateLimit-Remaining:
      schema: { type: integer }
    X-RateLimit-Reset:
      schema: { type: integer }
    Retry-After:
      schema: { type: integer }

  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
          required: [code, message]

    WorkspaceSummary:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        slug: { type: string }
        created_at: { type: string, format: date-time }
        needs_review_count: { type: integer }
        max_unreviewed_severity: { type: string, nullable: true }
        competitors_count: { type: integer }
        monitors_total: { type: integer }
        monitors_paused: { type: integer }

    CompetitorSummary:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        homepage_url: { type: string, format: uri }
        domain: { type: string }
        slug: { type: string }
        logo_url: { type: string, format: uri, nullable: true }
        discovery_status: { type: string, enum: [pending, running, completed, failed] }
        discovered_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }

    MonitorSummary:
      type: object
      properties:
        id: { type: integer }
        competitor_id: { type: integer }
        name: { type: string }
        url: { type: string, format: uri }
        monitor_type: { type: string }
        cadence: { type: string, enum: [hourly, six_hourly, daily, weekly] }
        status: { type: string, enum: [active, paused] }
        min_severity: { type: string, enum: [info, low, medium, high, critical] }
        proxy_kind: { type: string, enum: [datacenter, residential] }
        last_checked_at: { type: string, format: date-time, nullable: true }
        next_check_at: { type: string, format: date-time, nullable: true }
        consecutive_failures: { type: integer }
        last_run:
          type: object
          nullable: true
          properties:
            id: { type: integer }
            status: { type: string, enum: [pending, running, succeeded, failed, no_change, changed] }
            error_code: { type: string, nullable: true }
            error_message: { type: string, nullable: true }
            finished_at: { type: string, format: date-time, nullable: true }

    ChangeSummary:
      type: object
      properties:
        id: { type: integer }
        competitor_id: { type: integer }
        competitor: { type: string }
        monitor_type: { type: string }
        severity: { type: string, enum: [info, low, medium, high, critical] }
        summary: { type: string }
        recommended_action: { type: string, nullable: true }
        review_status: { type: string }
        created_at: { type: string, format: date-time }

paths:
  /workspaces:
    get:
      summary: List workspaces
      operationId: listWorkspaces
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  workspaces:
                    type: array
                    items: { $ref: "#/components/schemas/WorkspaceSummary" }
        "401": { description: Unauthorized, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
    post:
      summary: Create workspace
      operationId: createWorkspace
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
      responses:
        "201":
          description: Created
        "422": { description: Validation failed }

  /workspaces/{id}:
    get:
      summary: Get workspace
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: OK }
        "404": { description: Not found }

  /workspaces/{id}/digest:
    get:
      summary: Workspace digest
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
        - name: period
          in: query
          schema: { type: string, enum: [1d, 7d, 30d], default: 7d }
      responses:
        "200": { description: OK }

  /workspaces/{workspace_id}/competitors:
    get:
      summary: List competitors
      parameters:
        - name: workspace_id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  competitors:
                    type: array
                    items: { $ref: "#/components/schemas/CompetitorSummary" }
    post:
      summary: Add competitor
      parameters:
        - name: workspace_id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri }
                monitor_types:
                  type: array
                  items: { type: string, enum: [blog, changelog, pricing, docs, homepage] }
      responses:
        "202": { description: Discovery started }
        "200": { description: Existing competitor (duplicate URL) }
        "409":
          description: discovery_in_progress
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: object
                    properties:
                      code: { type: string }
                      message: { type: string }
                      active_competitor_id: { type: integer }

  /competitors/{id}:
    get:
      summary: Get competitor
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: OK }
    patch:
      summary: Update competitor
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                cadence: { type: string, enum: [hourly, six_hourly, daily, weekly] }
                status: { type: string, enum: [active, paused] }
      responses:
        "200": { description: OK }
    delete:
      summary: Delete competitor
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: OK }

  /competitors/{id}/discovery_status:
    get:
      summary: Discovery status
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: OK }

        "200": { description: OK }

  /workspaces/{id}/refresh:
    post:
      summary: Refresh workspace monitors
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                competitor_id: { type: integer }
      responses:
        "202": { description: Checks queued }
        "409": { description: discovery_in_progress }

  /monitors:
    get:
      summary: List monitors
      parameters:
        - name: workspace_id
          in: query
          required: true
          schema: { type: integer }
        - name: competitor_id
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  monitors:
                    type: array
                    items: { $ref: "#/components/schemas/MonitorSummary" }
        "422": { description: workspace_required }

        "422": { description: workspace_required }
    post:
      summary: Create monitor
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [competitor_id, url, monitor_type]
              properties:
                competitor_id: { type: integer }
                url: { type: string, format: uri }
                monitor_type: { type: string }
                cadence: { type: string, enum: [hourly, six_hourly, daily, weekly] }
      responses:
        "201": { description: Created }

  /monitors/{id}:
    get:
      summary: Get monitor
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
        - name: include
          in: query
          schema: { type: string }
          description: Comma-separated capture,capture_markdown
      responses:
        "200": { description: OK }
    patch:
      summary: Update monitor
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                url: { type: string }
                monitor_type: { type: string }
                cadence: { type: string, enum: [hourly, six_hourly, daily, weekly] }
                status: { type: string, enum: [active, paused] }
                min_severity: { type: string, enum: [info, low, medium, high, critical] }
      responses:
        "200": { description: OK }
    delete:
      summary: Delete monitor
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200": { description: OK }

  /monitors/{id}/check:
    post:
      summary: Trigger check
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "202": { description: Queued }
        "429": { description: refresh_throttled or quota_exceeded }

  /changes:
    get:
      summary: List changes
      parameters:
        - name: workspace_id
          in: query
          required: true
          schema: { type: integer }
        - name: competitor_id
          in: query
          schema: { type: integer }
        - name: review_status
          in: query
          schema: { type: string, enum: [unreviewed, acknowledged, dismissed] }
        - name: min_severity
          in: query
          schema: { type: string, enum: [info, low, medium, high, critical] }
        - name: since
          in: query
          schema: { type: string }
          description: ISO8601 or 1d, 7d, 30d
        - name: monitor_type
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 25, maximum: 100 }
        - name: after
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  changes:
                    type: array
                    items: { $ref: "#/components/schemas/ChangeSummary" }
                  has_more: { type: boolean }
                  next_cursor: { type: integer, nullable: true }
                  meta:
                    type: object
                    properties:
                      counts:
                        type: object
                        properties:
                          unreviewed: { type: integer }
                          acknowledged: { type: integer }
                          dismissed: { type: integer }
                      max_unreviewed_severity: { type: string, nullable: true }

  /changes/{id}:
    get:
      summary: Get change
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
        - name: include
          in: query
          schema: { type: string }
          description: Comma-separated capture,capture_markdown,raw_diff
      responses:
        "200": { description: OK }
    patch:
      summary: Update change review status
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [review_status]
              properties:
                review_status: { type: string, enum: [unreviewed, acknowledged, dismissed] }
      responses:
        "200": { description: OK }
