openapi: 3.1.0
info:
  title: AntiSlop Core Engine API
  version: 1.1.0
  summary: Score slop vs builder/product signal — score + why (not an LLM judge).
  description: |
    **For AI agents implementing a client:** read `/docs/antislop-agent-guide.md` first,
    then this OpenAPI. Flask source of truth: `meeting-scheduler/routes/antislop.py`.

    AntiSlop returns classical ML **score (0–10) + verdict + feature-backed why[]**.
    Dashboard, REST, and MCP share one Free/Pro/Enterprise verification quota.

    - Landing: https://www.insightits.com/products/antislop.html
    - Whitepaper: https://www.insightits.com/whitepapers/antislop.html
    - Agent guide: https://www.insightits.com/docs/antislop-agent-guide.md
    - MCP tool: https://www.insightits.com/mcp_tools/antislop_evaluate.json
  contact:
    name: Insight IT Solutions
    url: https://www.insightits.com
    email: info@insightits.com
  x-insightits-product: antislop
  x-ai-agent-instructions: |
    1. Call POST /evaluate (or /score) with JSON; never invent scores.
    2. Authenticate with X-API-Key ask_… for apps/MCP, or Bearer JWT for logged-in users.
    3. Handle 401, 422 (security block), 429 (quota/demo limit).
    4. Display why[].feature + why[].detail to end users.
    5. Same plan for REST and MCP — do not create a second billing SKU.
    6. Demo without auth is text-only, 10/IP/day — not for production agents.
    7. url_reference does not fetch URLs — client must put extracted text in raw_text.
    8. Full narrative guide: /docs/antislop-agent-guide.md

servers:
  - url: https://www.insightits.com/api/antislop/v1
    description: Production
  - url: http://localhost:5000/api/antislop/v1
    description: Local Flask

tags:
  - name: Evaluate
  - name: Membership
  - name: Keys
  - name: Meta

paths:
  /health:
    get:
      tags: [Meta]
      summary: Liveness
      operationId: health
      security: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
              example:
                status: ok
                service: antislop
                version: "1.0.0"

  /plans:
    get:
      tags: [Meta]
      summary: Public plan catalog (Free / Pro / Enterprise)
      operationId: listPlans
      security: []
      responses:
        "200":
          description: Plan amounts and verification limits
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlansResponse"

  /evaluate:
    post:
      tags: [Evaluate]
      summary: Evaluate content for slop vs builder/product signal
      operationId: evaluateContent
      description: |
        Primary endpoint. Counts as one verification against the member plan
        (or demo IP quota when unauthenticated).
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluateRequest"
            examples:
              linkedinPost:
                summary: Method A — LinkedIn post
                value:
                  content_type: text
                  source_platform: linkedin
                  raw_text: >-
                    We shipped agents to prod; p99 jumped from 120ms to 890ms after
                    the third loop. We added checkpoint replay instead of more tools.
                  include_decode: false
              readme:
                summary: Method B — README
                value:
                  content_type: text
                  source_platform: github_readme
                  raw_text: >-
                    # Orchestrator
                    Multi-agent DAG with evals, hybrid search, Docker/K8s, SSO and audit logs.
                  include_decode: false
      responses:
        "200":
          description: Score + why
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluateResponse"
        "400":
          description: Missing content_type
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          description: Unauthorized (invalid key/JWT, or demo used for non-text)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "422":
          description: Blocked by PrismGuard
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SecurityBlockResponse"
        "429":
          description: QuotaExceeded (member) or RateLimitExceeded (demo IP)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /score:
    post:
      tags: [Evaluate]
      summary: Alias of /evaluate
      operationId: scoreContent
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluateRequest"
      responses:
        "200":
          description: Same as /evaluate
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EvaluateResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/SecurityBlock"
        "429":
          $ref: "#/components/responses/RateLimited"

  /me:
    get:
      tags: [Membership]
      summary: Membership status and remaining quota
      operationId: getMe
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Plan + usage
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /keys:
    get:
      tags: [Keys]
      summary: List API keys (metadata only)
      operationId: listKeys
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Key list without secrets
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKeyMeta"
        "401":
          $ref: "#/components/responses/Unauthorized"
    post:
      tags: [Keys]
      summary: Create API key (plaintext returned once)
      operationId: createKey
      security:
        - BearerAuth: []
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                label:
                  type: string
      responses:
        "201":
          description: Created — store `key` immediately
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyCreated"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /keys/{key_id}:
    delete:
      tags: [Keys]
      summary: Revoke an API key
      operationId: revokeKey
      security:
        - BearerAuth: []
      parameters:
        - name: key_id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  id:
                    type: string
        "404":
          description: Not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /events:
    get:
      tags: [Membership]
      summary: Recent score events for the member
      operationId: listEvents
      security:
        - BearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        "200":
          description: Events
          content:
            application/json:
              schema:
                type: object
                properties:
                  events:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"

  /feedback:
    post:
      tags: [Evaluate]
      summary: Submit override / feedback for a prior request_id (gold training)
      operationId: submitFeedback
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FeedbackRequest"
      responses:
        "200":
          description: Accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  request_id:
                    type: string
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /draft-comment:
    post:
      tags: [Evaluate]
      summary: Optional PrismPost-style comment draft (never auto-posts)
      operationId: draftComment
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [raw_text]
              properties:
                raw_text:
                  type: string
                source_platform:
                  type: string
                score:
                  type: number
      responses:
        "200":
          description: Draft payload
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >
        InsightITS site JWT (OAuth/Google/password). Do not use for keys —
        keys use ApiKeyAuth or Bearer ask_…
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: >
        Member key from dashboard (prefix ask_). Also accepted as
        Authorization Bearer ask_…

  responses:
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    SecurityBlock:
      description: Security block
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/SecurityBlockResponse"
    RateLimited:
      description: Rate / quota limit
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"

  schemas:
    HealthResponse:
      type: object
      properties:
        status:
          type: string
          example: ok
        service:
          type: string
          example: antislop
        version:
          type: string

    ErrorResponse:
      type: object
      properties:
        error:
          type: string
        message:
          type: string

    SecurityBlockResponse:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
        threat_type:
          type: string
        request_id:
          type: string

    EvaluateRequest:
      type: object
      required: [content_type]
      properties:
        content_type:
          type: string
          enum: [text, image_base64, url_reference, pdf_base64]
          description: >
            Prefer `text` with client-extracted body. `url_reference` does not
            fetch URLs server-side yet — put extracted text in raw_text (warning
            digest_fallback_to_raw_text may appear). Demo auth allows `text` only.
        raw_text:
          type: string
          nullable: true
          description: Post body, README markdown, or extracted text (required for text; also used for url_reference today)
        image_data:
          type: string
          format: byte
          nullable: true
        pdf_data:
          type: string
          format: byte
          nullable: true
        source_platform:
          type: string
          enum:
            - linkedin
            - twitter_x
            - github_readme
            - product_spec
            - product_description
            - general_web
          default: general_web
        client_metadata:
          type: object
          additionalProperties: true
        include_decode:
          type: boolean
          default: false
          description: If true, return decode object; default score+why only
        options:
          type: object
          properties:
            decode:
              type: boolean
              default: false
            decode_engine:
              type: string
              enum: [v1, v2]
              default: v1
            polish_decode:
              type: boolean
              default: false
            prefer_vision:
              type: boolean
              default: false
            max_vision_pages:
              type: integer
              default: 3
              minimum: 1
              maximum: 10

    EvaluateResponse:
      type: object
      required: [request_id, score, verdict, why, diagnostics]
      properties:
        request_id:
          type: string
          description: Use with POST /feedback
        score:
          type: number
          minimum: 0
          maximum: 10
          description: 0 = pure slop/wrapper; 10 = high-signal
        verdict:
          type: string
          enum: [slop, weak, real, strong]
        method:
          type: string
          enum: [A, B]
          description: A = posts (PP-DETECT); B = product/README/PDF index
        archetype:
          type: string
          nullable: true
          enum:
            - The Wrapper Builder
            - The Deep Architect
            - The Vertical Operator
            - The Hype Maverick
            - The Open-Source Engine
        layer_scores:
          type: object
          nullable: true
          properties:
            execution_depth: { type: integer, minimum: 1, maximum: 5 }
            data_moat: { type: integer, minimum: 1, maximum: 5 }
            system_safeguards: { type: integer, minimum: 1, maximum: 5 }
            integration_density: { type: integer, minimum: 1, maximum: 5 }
            total_signal_score: { type: integer, minimum: 4, maximum: 20 }
        confidence:
          type: number
        why:
          type: array
          items:
            $ref: "#/components/schemas/WhyItem"
        diagnostics:
          $ref: "#/components/schemas/Diagnostics"
        decode:
          allOf:
            - $ref: "#/components/schemas/DecodeResult"
          nullable: true
        deterministic_verification:
          type: object
          properties:
            manifest_hash:
              type: string
            integer_drift_status:
              type: string
              enum: [PASSED_ZERO_DRIFT, DRIFT_CORRECTED]
        security_audit:
          type: object
          properties:
            prismguard_status:
              type: string
            sanitization_applied:
              type: boolean

    WhyItem:
      type: object
      required: [feature, direction, detail]
      properties:
        feature:
          type: string
        direction:
          type: string
          enum: [negative, positive, neutral]
        value:
          type: number
          nullable: true
        contribution:
          type: number
          nullable: true
        detail:
          type: string

    DecodeResult:
      type: object
      description: Present only when include_decode=true
      properties:
        intent: { type: string }
        intent_confidence: { type: number }
        idea_behind: { type: string }
        surface_claim: { type: string }
        evidence_spans:
          type: array
          items: { type: string }
        missing_evidence:
          type: array
          items: { type: string }
        template_fingerprint: { type: string, nullable: true }
        persuasion_tactics:
          type: array
          items: { type: string }
        what_would_make_it_real:
          type: array
          items: { type: string }
        mode:
          type: string
          enum: [niche_insight, slop_play]
        niche_idea: { type: string, nullable: true }
        niche_tags:
          type: array
          items: { type: string }
        roles:
          type: array
          items:
            type: object
            properties:
              span: { type: string }
              role: { type: string }
              start: { type: integer }
              end: { type: integer }
        performed_claim: { type: string }
        supported_claim: { type: string }
        wedge: { type: string }
        play_grammar:
          type: array
          items: { type: string }
        falsifiability: { type: number, minimum: 0, maximum: 1 }
        needs_sharpening: { type: boolean }
        niche_card_id: { type: string, nullable: true }
        counterfactuals:
          type: array
          items:
            type: object
            properties:
              fix: { type: string }
              feature: { type: string }
              expected_score_delta: { type: number }
        decode_engine_used:
          type: string
          enum: [v1, v2]

    Diagnostics:
      type: object
      required: [summary, action_recommendation]
      properties:
        summary: { type: string }
        positive_signals:
          type: array
          items: { type: string }
        red_flags:
          type: array
          items: { type: string }
        action_recommendation: { type: string }

    MeResponse:
      type: object
      properties:
        plan:
          type: string
          enum: [free, pro, enterprise]
        verifications_used: { type: integer }
        verifications_limit: { type: integer }
        seats: { type: integer, nullable: true }

    PlansResponse:
      type: object
      properties:
        plans:
          type: object
          additionalProperties:
            type: object
            properties:
              plan: { type: string }
              amount_usd: { type: number }
              verifications: { type: integer }
              seats: { type: integer }
              product_code: { type: string }
              price_id: { type: string, nullable: true }

    ApiKeyMeta:
      type: object
      properties:
        id: { type: string }
        prefix: { type: string }
        label: { type: string, nullable: true }
        created_at: { type: string }
        revoked_at: { type: string, nullable: true }

    ApiKeyCreated:
      type: object
      properties:
        id: { type: string }
        key:
          type: string
          description: Plaintext ask_… — shown once
        prefix: { type: string }
        label: { type: string, nullable: true }
        message: { type: string }

    FeedbackRequest:
      type: object
      required: [request_id]
      properties:
        request_id: { type: string }
        override_verdict: { type: string }
        feedback: { type: string }
        override_score: { type: number }
