openapi: 3.1.0
info:
  title: AgentGuard API
  description: |
    **AgentGuard** is the security guardian for AI agents. It scans skills, tools, plugins, and MCP servers
    for threats including prompt injection, credential leaks, malicious commands, data exfiltration,
    permission abuse, and suspicious URLs.

    ## Authentication

    AgentGuard supports two authentication methods:

    ### 1. API Key (Subscription)
    Include your API key in every request via the `X-API-Key` header:
    ```
    X-API-Key: ag_live_your_key_here
    ```
    Get your API key at [agentguard.gopluslabs.io/api-keys](https://agentguard.gopluslabs.io/api-keys).

    ### 2. x402 Micropayment (Pay-Per-Scan)
    No API key needed. AI agents pay $0.001 USDC per scan via the [x402 protocol](https://www.x402.org/) on Base Sepolia.
    If no API key is provided, the server responds with HTTP 402 containing payment instructions.

    ## Rate Limits

    | Tier | Requests/min | Scans/month | Max API Keys |
    |------|-------------|-------------|--------------|
    | Free | 5 | 100 | 1 |
    | Pro | 50 | 5,000 | 5 |
    | Enterprise | 200 | Unlimited | 20 |

    Rate limit headers are included in every response:
    - `X-RateLimit-Limit` — Max requests per minute
    - `X-RateLimit-Remaining` — Remaining requests in current window
    - `X-RateLimit-Reset` — Unix timestamp when the window resets

    ## Security Detectors

    AgentGuard runs 8 specialized detectors in parallel:

    | Detector | Description | Rules |
    |----------|-------------|-------|
    | `credential_leak` | Hardcoded API keys, secrets, private keys | 146+ patterns |
    | `prompt_injection` | Instruction overrides, role hijacking, obfuscation | 75+ patterns |
    | `malicious_command` | Destructive commands, RCE, reverse shells, crypto mining | 100+ patterns |
    | `data_exfiltration` | Sensitive file access + external transmission | 40+ patterns |
    | `permission_abuse` | Over-privileged tool declarations, dangerous combos | Heuristic |
    | `url_analyzer` | Malicious domains, phishing, URL shorteners, paste services | 50+ patterns |
    | `social_engineering` | Urgency tactics, authority impersonation, trust manipulation | Heuristic |
    | `ai_analyzer` | Intent mismatch, hidden functionality, semantic attacks (opt-in) | LLM-powered |

  version: 1.0.0
  contact:
    name: GoPlus Security
    url: https://gopluslabs.io
    email: security@gopluslabs.io
  license:
    name: MIT
    url: https://opensource.org/licenses/MIT

servers:
  - url: https://agentguard.gopluslabs.io
    description: Production

tags:
  - name: Scanning
    description: Scan skills, URLs, and registries for security threats
  - name: Reports
    description: Retrieve stored scan reports
  - name: Health
    description: Service health check

security:
  - ApiKeyAuth: []

paths:
  /api/v1/scan:
    post:
      operationId: scanContent
      summary: Scan skill content
      description: |
        Scan raw skill/code content for security threats. Returns a full report with risk score,
        verdict, detected threats, and permission analysis.

        The content should be the full skill file including YAML frontmatter (if present).
      tags: [Scanning]
      security:
        - ApiKeyAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScanRequest'
            examples:
              simple:
                summary: Simple skill scan
                value:
                  content: |
                    ---
                    name: file-reader
                    description: Read files from disk
                    allowed-tools: Read, Glob, Grep
                    ---
                    Read the specified file and return its contents.
              suspicious:
                summary: Suspicious skill with credential leak
                value:
                  content: |
                    ---
                    name: deploy-helper
                    allowed-tools: Bash(*), Write
                    ---
                    Run this command to deploy:
                    curl -H "Authorization: Bearer sk-proj-abc123" https://api.openai.com/v1/chat
                  context:
                    registry: npm
                    author: unknown-author
      responses:
        '200':
          description: Scan completed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScanReportResponse'
              example:
                success: true
                data:
                  scanId: scan_a1b2c3d4e5f6
                  riskScore: 85
                  riskLevel: critical
                  verdict: blocked
                  summary: "Detected 2 critical threats: hardcoded OpenAI API key, unrestricted Bash access. Skill should NOT be installed."
                  threats:
                    - detector: credential_leak
                      severity: critical
                      title: "Hardcoded OpenAI API key detected"
                      description: "Found an OpenAI API key (sk-proj-...) embedded directly in the skill content."
                      evidence: "sk-proj-abc123"
                      line: 6
                      remediation: "Remove the API key and use environment variables instead."
                      cwe: "CWE-798"
                    - detector: permission_abuse
                      severity: critical
                      title: "Unrestricted Bash access requested"
                      description: "Skill requests Bash(*) which grants full shell access without restrictions."
                      evidence: "Bash(*)"
                      remediation: "Limit to specific, necessary tools like Read, Glob, Grep."
                      cwe: "CWE-250"
                  permissions:
                    declared: ["Bash(*)", "Write"]
                    recommended: ["Read", "Glob"]
                    riskDelta: critical
                  processingMs: 12
                meta:
                  requestId: req_abc123xyz456
                  rateLimit:
                    limit: 5
                    remaining: 4
                    reset: 1709726400
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Payment required (x402). Response includes payment instructions for USDC on Base Sepolia.
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /api/v1/scan-url:
    post:
      operationId: scanUrl
      summary: Scan skill from URL
      description: |
        Fetch a skill file from a URL and scan it for security threats. Supports GitHub repository URLs
        (automatically converted to raw content), ClawHub URLs, and raw file URLs.

        For GitHub URLs, provide the blob URL (e.g., `https://github.com/user/repo/blob/main/SKILL.md`)
        and it will be automatically converted to the raw content URL.
      tags: [Scanning]
      security:
        - ApiKeyAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScanUrlRequest'
            examples:
              github:
                summary: Scan from GitHub
                value:
                  url: "https://github.com/user/repo/blob/main/skills/trading-bot/SKILL.md"
                  type: github
              raw:
                summary: Scan from raw URL
                value:
                  url: "https://raw.githubusercontent.com/user/repo/main/SKILL.md"
                  type: raw
              agentskill:
                summary: Scan from agentskill.sh
                value:
                  url: "https://agentskill.sh/@alsk1992/opinion/SKILL.md"
                  type: raw
      responses:
        '200':
          description: Scan completed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScanReportResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: Payment required (x402)
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'

  /api/v1/scan-registry:
    post:
      operationId: scanRegistry
      summary: Batch scan skills from registry
      description: |
        Queue a batch scan of skills from a registry. Returns a batch ID for tracking.

        **Requires Pro or Enterprise tier** (or x402 payment).

        > **Note:** This endpoint currently queues the batch for processing and returns immediately
        > with a 202 status. Batch results will be available via the report endpoint.
      tags: [Scanning]
      security:
        - ApiKeyAuth: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScanRegistryRequest'
            example:
              registry: agentskill.sh
              limit: 50
              offset: 0
              filter: latest
      responses:
        '202':
          description: Batch scan queued
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      batchId:
                        type: string
                        example: batch_a1b2c3d4e5
                      registry:
                        type: string
                        example: agentskill.sh
                      status:
                        type: string
                        enum: [processing]
                        example: processing
                      limit:
                        type: integer
                        example: 50
                      offset:
                        type: integer
                        example: 0
                      filter:
                        type: string
                        example: latest
                      message:
                        type: string
                        example: "Batch scan has been queued for processing."
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Feature not available on current tier
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                success: false
                error:
                  code: ERROR
                  message: "Batch registry scanning is only available on Pro and Enterprise plans."
        '429':
          $ref: '#/components/responses/RateLimited'

  /api/v1/report/{scanId}:
    get:
      operationId: getReport
      summary: Get scan report
      description: |
        Retrieve a previously stored scan report by its scan ID.
        You can only access reports for scans performed with your own API key.
      tags: [Reports]
      security:
        - ApiKeyAuth: []
      parameters:
        - name: scanId
          in: path
          required: true
          description: The scan ID returned from a previous scan request
          schema:
            type: string
            example: scan_a1b2c3d4e5f6
      responses:
        '200':
          description: Report retrieved successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    allOf:
                      - $ref: '#/components/schemas/ScanReport'
                      - type: object
                        properties:
                          createdAt:
                            type: string
                            format: date-time
                            description: When the scan was performed
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: No permission to view this report
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Scan report not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'

  /api/v1/status:
    get:
      operationId: getStatus
      summary: Health check
      description: Returns the service health status. No authentication required.
      tags: [Health]
      security: []
      responses:
        '200':
          description: Service is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    type: object
                    properties:
                      status:
                        type: string
                        example: healthy
                      version:
                        type: string
                        example: "1.0.0"
                      timestamp:
                        type: string
                        format: date-time

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API key for authenticated access. Get yours at
        [agentguard.gopluslabs.io/api-keys](https://agentguard.gopluslabs.io/api-keys).

        Format: `ag_live_` followed by a random string.

  schemas:
    ScanRequest:
      type: object
      required: [content]
      properties:
        content:
          type: string
          description: |
            The full skill/code content to scan. Can include YAML frontmatter with metadata
            (name, description, allowed-tools) followed by the skill body.
          example: |
            ---
            name: my-skill
            description: A helper skill
            allowed-tools: Read, Glob
            ---
            Read the file and summarize it.
        ai:
          type: boolean
          default: false
          description: |
            Enable AI-powered deep analysis using LLM. Detects intent mismatch, hidden functionality,
            and semantic attacks that rule-based engines cannot catch. Adds ~5-30s to processing time.
        files:
          type: array
          description: Optional additional files associated with the skill
          items:
            $ref: '#/components/schemas/SkillFile'
        context:
          $ref: '#/components/schemas/ScanContext'

    SkillFile:
      type: object
      required: [path, content]
      properties:
        path:
          type: string
          description: File path relative to the skill root
          example: "lib/helpers.py"
        content:
          type: string
          description: File content
          example: "import os\ndef get_env(key): return os.environ[key]"

    ScanContext:
      type: object
      description: Optional metadata about the scan source
      properties:
        registry:
          type: string
          description: Source registry name
          example: agentskill.sh
        author:
          type: string
          description: Skill author identifier
          example: "@alsk1992"
        version:
          type: string
          description: Skill version
          example: "1.2.0"

    ScanUrlRequest:
      type: object
      required: [url, type]
      properties:
        url:
          type: string
          format: uri
          description: URL of the skill file to fetch and scan
          example: "https://github.com/user/repo/blob/main/SKILL.md"
        type:
          type: string
          enum: [github, clawhub, raw]
          description: |
            URL type:
            - `github` — GitHub blob URL (auto-converted to raw)
            - `clawhub` — ClawHub skill URL
            - `raw` — Direct raw content URL (works for any publicly accessible URL)

    ScanRegistryRequest:
      type: object
      required: [registry]
      properties:
        registry:
          type: string
          description: Registry name to scan
          example: agentskill.sh
        limit:
          type: integer
          default: 50
          minimum: 1
          maximum: 500
          description: Maximum number of skills to scan
        offset:
          type: integer
          default: 0
          minimum: 0
          description: Pagination offset
        filter:
          type: string
          enum: [latest, all, flagged]
          default: latest
          description: |
            Filter skills:
            - `latest` — Most recently published
            - `all` — All skills
            - `flagged` — Previously flagged as suspicious

    ScanReport:
      type: object
      properties:
        scanId:
          type: string
          description: Unique scan identifier
          example: scan_a1b2c3d4e5f6
        riskScore:
          type: integer
          minimum: 0
          maximum: 100
          description: |
            Composite risk score (0-100). Calculated from weighted sum of threat severities:
            - info: 2, low: 5, medium: 15, high: 30, critical: 50
          example: 85
        riskLevel:
          $ref: '#/components/schemas/RiskLevel'
        verdict:
          $ref: '#/components/schemas/Verdict'
        summary:
          type: string
          description: Human-readable summary of findings
          example: "Detected 2 critical threats: hardcoded API key, unrestricted Bash access."
        threats:
          type: array
          items:
            $ref: '#/components/schemas/Threat'
        permissions:
          $ref: '#/components/schemas/PermissionAnalysis'
        processingMs:
          type: integer
          description: Processing time in milliseconds
          example: 12

    Threat:
      type: object
      properties:
        detector:
          $ref: '#/components/schemas/DetectorName'
        severity:
          $ref: '#/components/schemas/Severity'
        title:
          type: string
          description: Short threat title
          example: "Hardcoded OpenAI API key detected"
        description:
          type: string
          description: Detailed explanation of the threat
          example: "Found an OpenAI API key embedded directly in the skill content."
        evidence:
          type: string
          description: The specific text/pattern that triggered the detection
          example: "sk-proj-abc123..."
        line:
          type: integer
          description: Line number where the threat was found (if applicable)
          example: 6
        remediation:
          type: string
          description: Suggested fix
          example: "Remove the API key and use environment variables instead."
        cwe:
          type: string
          description: Common Weakness Enumeration ID
          example: "CWE-798"

    PermissionAnalysis:
      type: object
      properties:
        declared:
          type: array
          items:
            type: string
          description: Tools declared in the skill's allowed-tools
          example: ["Bash(*)", "Write"]
        recommended:
          type: array
          items:
            type: string
          description: Minimal set of tools recommended based on skill purpose
          example: ["Read", "Glob", "Grep"]
        riskDelta:
          $ref: '#/components/schemas/RiskLevel'

    RiskLevel:
      type: string
      enum: [safe, low, medium, high, critical]
      description: |
        Risk level based on score thresholds:
        - `safe` (0-14) — No threats detected
        - `low` (15-39) — Minor issues only
        - `medium` (40-64) — Some concerning patterns
        - `high` (65-84) — Significant threats found
        - `critical` (85-100) — Dangerous, should not be installed
      example: critical

    Verdict:
      type: string
      enum: [passed, warning, blocked]
      description: |
        Final verdict:
        - `passed` — Safe to install (risk: safe or low)
        - `warning` — Review recommended (risk: medium)
        - `blocked` — Do not install (risk: high or critical)
      example: blocked

    Severity:
      type: string
      enum: [info, low, medium, high, critical]
      example: critical

    DetectorName:
      type: string
      enum:
        - credential_leak
        - prompt_injection
        - malicious_command
        - data_exfiltration
        - permission_abuse
        - url_analyzer
        - social_engineering
        - ai_analyzer
      description: |
        The security detector that found this threat:
        - `credential_leak` — Hardcoded secrets, API keys, private keys
        - `prompt_injection` — Instruction overrides, role hijacking, obfuscation
        - `malicious_command` — Destructive commands, RCE, reverse shells
        - `data_exfiltration` — Sensitive file access + external transmission
        - `permission_abuse` — Over-privileged tool declarations
        - `url_analyzer` — Malicious domains, phishing, paste services
        - `social_engineering` — Urgency tactics, authority impersonation, trust manipulation
        - `ai_analyzer` — Intent mismatch, hidden functionality, semantic attacks (opt-in via `ai: true`)

    ScanReportResponse:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: '#/components/schemas/ScanReport'
        meta:
          type: object
          properties:
            requestId:
              type: string
              example: req_abc123xyz456
            rateLimit:
              type: object
              properties:
                limit:
                  type: integer
                  example: 5
                remaining:
                  type: integer
                  example: 4
                reset:
                  type: integer
                  description: Unix timestamp
                  example: 1709726400

    ErrorResponse:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: object
          properties:
            code:
              type: string
              example: ERROR
            message:
              type: string
              example: "Field 'content' is required and must be a non-empty string."
        meta:
          type: object
          properties:
            requestId:
              type: string
              example: req_xyz789abc123

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: ERROR
              message: "Field 'content' is required and must be a non-empty string."
            meta:
              requestId: req_xyz789abc123

    Unauthorized:
      description: Invalid or missing API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: AUTHENTICATION_ERROR
              message: "Invalid or missing API key."
            meta:
              requestId: req_xyz789abc123

    RateLimited:
      description: Too many requests
      headers:
        Retry-After:
          schema:
            type: integer
            example: 60
          description: Seconds to wait before retrying
        X-RateLimit-Limit:
          schema:
            type: integer
          description: Max requests per minute
        X-RateLimit-Remaining:
          schema:
            type: integer
          description: Remaining requests in window
        X-RateLimit-Reset:
          schema:
            type: integer
          description: Unix timestamp when window resets
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: RATE_LIMIT_EXCEEDED
              message: "Rate limit exceeded. Please wait before making more requests."
            meta:
              requestId: req_xyz789abc123

    InternalError:
      description: Server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            success: false
            error:
              code: ERROR
              message: "Internal server error"
            meta:
              requestId: req_xyz789abc123
