{
  "openapi": "3.1.0",
  "info": {
    "title": "AccessiSight Scan API",
    "version": "1.0.0",
    "description": "Trigger accessibility scans and fetch results programmatically, with the same detection engine and credit balance as the AccessiSight dashboard, driven from your own code or CI pipeline. See https://accessisight.com/developers for the human-readable guide, including webhook signature verification.",
    "contact": {
      "url": "https://accessisight.com/developers"
    }
  },
  "servers": [
    { "url": "https://accessisight.com", "description": "Production" }
  ],
  "security": [{ "bearerAuth": [] }],
  "paths": {
    "/api/v1/scans": {
      "post": {
        "operationId": "createScan",
        "summary": "Start a scan",
        "description": "Starts a scan and returns immediately with a scan id. Scanning a real page takes 15-30 seconds, so this endpoint does not block. Consumes 1 credit from the same balance the dashboard draws from.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CreateScanRequest" },
              "example": {
                "url": "https://example.com",
                "tags": ["wcag2aa"],
                "captureScreenshot": true,
                "webhookUrl": "https://your-app.com/webhooks/accessisight"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Scan accepted and queued",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CreateScanResponse" }
              }
            }
          },
          "400": { "description": "Missing/invalid `url` or `webhookUrl`", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "402": { "description": "No credits remaining", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "429": { "description": "Rate limited", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    },
    "/api/v1/scans/{id}": {
      "get": {
        "operationId": "getScan",
        "summary": "Fetch a scan's status and results",
        "description": "Poll this until `status` is `completed` or `failed`, or use `webhookUrl` on creation instead. Only visible to the API key that created it: 404 (not 403) if the scan doesn't exist or belongs to a different account, so a caller can't distinguish the two.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": { "type": "string" },
            "description": "The scanId returned by POST /api/v1/scans"
          }
        ],
        "responses": {
          "200": {
            "description": "The full scan record",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScanResult" }
              }
            }
          },
          "401": { "description": "Invalid or missing API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
          "404": { "description": "Scan not found (or not owned by this API key)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Generate a key in Settings → API Access. Shown once at generation time; only its hash is stored, and it cannot be retrieved again."
      }
    },
    "schemas": {
      "CreateScanRequest": {
        "type": "object",
        "required": ["url"],
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "Public HTTP(S) URL to scan. Internal/private addresses are rejected." },
          "tags": { "type": "array", "items": { "type": "string" }, "description": "Standard filters, e.g. wcag2aa, section508. Defaults to all." },
          "captureScreenshot": { "type": "boolean", "default": true, "description": "Set false to skip screenshot-driven checks and speed up the scan." },
          "includeAiAnalysis": { "type": "boolean", "default": true },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "description": "Public HTTPS URL to POST the result to on completion. The request carries an X-AccessiSight-Signature header (t=<unix>,v1=<hex hmac-sha256>, the same shape Stripe uses) signed with the webhook secret shown in Settings → API Access, next to your API key."
          }
        }
      },
      "CreateScanResponse": {
        "type": "object",
        "properties": {
          "scanId": { "type": "string" },
          "status": { "type": "string", "enum": ["scanning"] },
          "creditsRemaining": { "type": "integer" }
        }
      },
      "ScanResult": {
        "type": "object",
        "description": "The full persisted scan record. Fields below are the stable, documented subset; the response may include additional internal fields.",
        "properties": {
          "id": { "type": "string" },
          "url": { "type": "string" },
          "status": { "type": "string", "enum": ["scanning", "completed", "failed", "partial"] },
          "timestamp": { "type": "string", "format": "date-time" },
          "error": { "type": "string", "description": "Present only when status is 'failed'." },
          "summary": {
            "type": "object",
            "properties": {
              "critical": { "type": "integer" },
              "serious": { "type": "integer" },
              "moderate": { "type": "integer" },
              "minor": { "type": "integer" },
              "total": { "type": "integer" },
              "score": { "type": "number", "description": "WCAG 3.0-preview composite score, 0-100. A future-readiness indicator, not a compliance status. WCAG 3.0 is a W3C Working Draft, not a finished Recommendation." }
            }
          },
          "violations": { "type": "array", "items": { "type": "object" }, "description": "Rule-engine findings (core-rules + PDF/UA structural findings for a scanned PDF)." },
          "heuristics": { "type": "array", "items": { "type": "object" }, "description": "Findings from the heuristics/KITS+/NLP/dark-pattern detection layers." },
          "screenshot": { "type": "string", "description": "Base64-encoded PNG of the scanned page (present when captureScreenshot was true)." }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": { "type": "string" }
        }
      }
    }
  }
}
