{
  "openapi": "3.1.0",
  "info": {
    "title": "Pre-Trip Scan API",
    "version": "1.0.0",
    "description": "Scan email, landing pages, and ad creative for regulated-health marketing compliance. Drafts are processed in memory and never stored. Create an API key in your account under 'API keys'."
  },
  "servers": [
    { "url": "https://scan.kesey.agency/api/v1" }
  ],
  "security": [
    { "bearerAuth": [] }
  ],
  "paths": {
    "/scan": {
      "post": {
        "operationId": "scan",
        "summary": "Scan a draft against applicable rulesets",
        "description": "Resolves which rulesets apply from the facets, then returns a deterministic pass or fix list plus an advisory pass. Spends the account's Bus Pass or tokens; the free trial is web-only.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ScanRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scan complete.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ScanResponse" }
              }
            }
          },
          "400": { "description": "Bad request; a field is missing or invalid." },
          "401": { "description": "Missing, malformed, or revoked API key." },
          "402": { "description": "Out of scans; no pass or tokens remaining." },
          "422": { "description": "A URL would not load or an image was unreadable. Nothing was spent." },
          "429": { "description": "Over the per-key rate limit. See the Retry-After header." }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key created in your account, sent as 'Authorization: Bearer pt_live_...'."
      }
    },
    "schemas": {
      "ScanRequest": {
        "type": "object",
        "required": ["asset_type", "state", "vertical"],
        "properties": {
          "source": {
            "type": "string",
            "enum": ["paste", "url", "image"],
            "default": "paste",
            "description": "How the draft is supplied."
          },
          "asset_type": {
            "type": "string",
            "enum": ["email", "landing_page", "blog", "social"],
            "description": "The kind of asset being scanned."
          },
          "state": {
            "type": "string",
            "pattern": "^[A-Za-z]{2}$",
            "description": "Two-letter state code marketed into."
          },
          "vertical": {
            "type": "string",
            "description": "The regulated vertical. See /api/library for the current vocabulary."
          },
          "covered_entity": {
            "type": "boolean",
            "default": false,
            "description": "True if the advertiser is a HIPAA covered entity."
          },
          "subject": {
            "type": "string",
            "maxLength": 500,
            "description": "Email subject and preview text. Email only."
          },
          "body": {
            "type": "string",
            "maxLength": 60000,
            "description": "The draft copy, for source paste. HTML or plain text."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "The page to fetch and scan, for source url."
          },
          "image": {
            "type": "object",
            "description": "For source image.",
            "required": ["media_type", "data_b64"],
            "properties": {
              "media_type": {
                "type": "string",
                "enum": ["image/png", "image/jpeg", "image/webp", "image/gif"]
              },
              "data_b64": {
                "type": "string",
                "description": "Base64-encoded image bytes."
              }
            }
          }
        }
      },
      "ScanResponse": {
        "type": "object",
        "properties": {
          "scan": {
            "type": "object",
            "description": "The deterministic verdict: verdict, findings, cleared rulesets, and standing checks."
          },
          "ai": {
            "type": "object",
            "description": "The advisory pass. It can add nuance but never clears a finding."
          },
          "intake": {
            "type": "object",
            "description": "The intake mode, and for url or image the exact text that was scanned."
          },
          "spend": {
            "type": "object",
            "description": "Which entitlement was spent and what remains.",
            "properties": {
              "source": { "type": "string", "enum": ["pass", "token", "operator", "none"] },
              "tokens_remaining": { "type": ["integer", "null"] },
              "pass_remaining": { "type": ["integer", "null"] }
            }
          }
        }
      }
    }
  }
}
