openapi: 3.1.0
info:
  title: Detecte API
  version: "0.1.0"
  description: |
    Runtime security for AI agents. Verify every action before it runs.
    Production base URL: `https://api.detecte.xyz`.
  contact:
    name: Detecte
    url: https://detecte.xyz
    email: support@detecte.xyz
  license:
    name: Proprietary

servers:
  - url: https://api.detecte.xyz
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - { name: Verify, description: Real-time policy decisions }
  - { name: Agents, description: Register and manage agents }
  - { name: Policies, description: Policy CRUD + dry-run }
  - { name: Sessions, description: Group multi-step flows }
  - { name: Approvals, description: Human-in-the-loop }
  - { name: Incidents }
  - { name: Audit }
  - { name: Scans, description: KYA verification battery }
  - { name: Keys, description: Workspace-scoped API keys }
  - { name: Streams, description: Server-Sent Events }

paths:
  /v1/verify:
    post:
      tags: [Verify]
      summary: Verify an action
      description: |
        The single most important endpoint. Pass an action and Detecte returns
        a decision based on your policies, the agent's tier, and behavioral
        baselines. p50 latency &lt; 60ms.
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/VerifyInput" }
      responses:
        "200":
          description: Decision
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Decision" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { description: Unknown agent }
        "422": { $ref: "#/components/responses/ValidationError" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /v1/agents:
    get:
      tags: [Agents]
      summary: List agents
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 50, maximum: 200 } }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: "#/components/schemas/Agent" } }
    post:
      tags: [Agents]
      summary: Register an agent
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AgentInput" }
      responses:
        "200":
          content: { application/json: { schema: { $ref: "#/components/schemas/Agent" } } }
          description: Created

  /v1/agents/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    get:
      tags: [Agents]
      summary: Get an agent
      responses:
        "200":
          content: { application/json: { schema: { $ref: "#/components/schemas/Agent" } } }
          description: OK
    patch:
      tags: [Agents]
      summary: Update an agent
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AgentInput" }
      responses:
        "200":
          content: { application/json: { schema: { $ref: "#/components/schemas/Agent" } } }
          description: OK

  /v1/agents/{id}/verify:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    post:
      tags: [Scans]
      summary: Run the KYA verification battery
      description: Runs the full KYA test battery against the agent. May take 60-180 seconds.
      responses:
        "200":
          content: { application/json: { schema: { $ref: "#/components/schemas/ScanResult" } } }
          description: Battery complete

  /v1/policies:
    get:
      tags: [Policies]
      summary: List policies
      responses: { "200": { description: OK } }
    post:
      tags: [Policies]
      summary: Create a policy
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PolicyInput" }
      responses: { "200": { description: Created } }

  /v1/policies/{id}:
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    get: { tags: [Policies], summary: Get a policy, responses: { "200": { description: OK } } }
    patch: { tags: [Policies], summary: Update a policy, responses: { "200": { description: OK } } }
    delete: { tags: [Policies], summary: Delete a policy, responses: { "200": { description: OK } } }

  /v1/policies/dry-run:
    post:
      tags: [Policies]
      summary: Dry-run a policy against historical actions
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [policy]
              properties:
                policy: { $ref: "#/components/schemas/PolicyInput" }
                sample_size: { type: integer, default: 1000, maximum: 5000 }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  sample_size: { type: integer }
                  would_have_blocked: { type: integer }
                  would_have_escalated: { type: integer }
                  would_have_required_approval: { type: integer }
                  would_have_allowed: { type: integer }
                  examples: { type: array, items: { type: object } }

  /v1/sessions:
    post: { tags: [Sessions], summary: Open a session, responses: { "200": { description: OK } } }
  /v1/sessions/{id}/close:
    post:
      tags: [Sessions]
      summary: Close a session
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: OK } }

  /v1/approvals/{id}:
    parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
    get: { tags: [Approvals], summary: Get approval status, responses: { "200": { description: OK } } }
    post:
      tags: [Approvals]
      summary: Approve or reject (used by the dashboard)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                approved: { type: boolean }
                reason: { type: string }
      responses: { "200": { description: OK } }

  /v1/incidents:
    get: { tags: [Incidents], summary: List incidents, responses: { "200": { description: OK } } }
  /v1/incidents/{id}/resolve:
    parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
    post: { tags: [Incidents], summary: Mark resolved, responses: { "200": { description: OK } } }

  /v1/audit:
    get:
      tags: [Audit]
      summary: Audit log
      parameters:
        - { name: limit, in: query, schema: { type: integer, default: 100, maximum: 500 } }
        - { name: agent, in: query, schema: { type: string } }
        - { name: action, in: query, schema: { type: string } }
        - { name: status, in: query, schema: { type: string } }
      responses: { "200": { description: OK } }

  /v1/keys:
    get: { tags: [Keys], summary: List keys, responses: { "200": { description: OK } } }
    post:
      tags: [Keys]
      summary: Create a key (raw value shown once)
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                env: { type: string, enum: [test, live], default: test }
                name: { type: string }
      responses: { "200": { description: Created } }
  /v1/keys/{id}:
    delete:
      tags: [Keys]
      summary: Revoke a key
      parameters: [{ name: id, in: path, required: true, schema: { type: string } }]
      responses: { "200": { description: Revoked } }

  /v1/stream/decisions:
    get:
      tags: [Streams]
      summary: SSE stream of new decisions for the workspace
      responses:
        "200":
          description: text/event-stream
          content:
            text/event-stream:
              schema: { type: string }
  /v1/stream/incidents:
    get:
      tags: [Streams]
      summary: SSE stream of new incidents
      responses:
        "200":
          description: text/event-stream
          content:
            text/event-stream:
              schema: { type: string }

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_test_… or sk_live_…

  responses:
    Unauthorized:
      description: Missing or invalid API key
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    ValidationError:
      description: Input failed validation
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    RateLimited:
      description: Too many requests
      headers:
        Retry-After: { schema: { type: integer }, description: Seconds until you may retry }
        X-RateLimit-Limit: { schema: { type: integer } }
        X-RateLimit-Remaining: { schema: { type: integer } }
        X-RateLimit-Reset: { schema: { type: integer } }
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }

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

    VerifyInput:
      type: object
      required: [agent, action]
      properties:
        agent: { type: string, description: agent id or name }
        action: { type: string, example: refund_order }
        params: { type: object, additionalProperties: true }
        context: { type: object, additionalProperties: true }
        sensitive:
          type: array
          items: { type: string }
          description: Param keys to HMAC-hash before storage
        sessionId: { type: string }
        idempotencyKey: { type: string }

    Decision:
      type: object
      required: [id, allowed, status]
      properties:
        id: { type: string }
        allowed: { type: boolean }
        status: { type: string, enum: [allowed, blocked, escalated, pending_approval] }
        reason: { type: [string, "null"] }
        policies_evaluated:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              name: { type: string }
              result: { type: string }
        risk_delta: { type: number }
        approval_url: { type: [string, "null"] }
        expires_at: { type: string, format: date-time }
        metadata:
          type: object
          properties:
            latency_ms: { type: integer }

    Agent:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        description: { type: string }
        tier: { type: string, enum: [low, medium, high, restricted] }
        declared_capabilities: { type: array, items: { type: string } }
        risk_score: { type: integer, minimum: 0, maximum: 100 }
        created_at: { type: string, format: date-time }
    AgentInput:
      type: object
      required: [name]
      properties:
        name: { type: string }
        description: { type: string }
        declared_capabilities: { type: array, items: { type: string } }
        tier: { type: string, enum: [low, medium, high, restricted] }

    PolicyInput:
      type: object
      required: [name, when, then]
      properties:
        name: { type: string }
        agents: { type: array, items: { type: string } }
        when: { type: object, additionalProperties: true }
        then:
          type: object
          required: [decision]
          properties:
            decision: { type: string, enum: [allow, block, escalate, require_approval] }
            message: { type: string }
            approvers: { type: array, items: { type: string } }
        enabled: { type: boolean, default: true }

    ScanResult:
      type: object
      properties:
        agent_id: { type: string }
        score: { type: integer, minimum: 0, maximum: 100 }
        tier: { type: string }
        tests_passed: { type: integer }
        tests_total: { type: integer }
        credential: { type: string, description: signed JWT }
        expires_at: { type: string, format: date-time }
