openapi: 3.1.0 info: # Do not change the title, if the title changes, the import paths will be broken title: Api version: 0.1.0 description: API specification servers: - url: /api description: Base API path tags: - name: health description: Health operations - name: scans description: Skill scans and audit reports - name: providers description: Configurable external AI providers - name: prompts description: Configurable AI analysis prompts - name: rules description: Static rule catalog configuration - name: dashboard description: Dashboard summaries paths: /healthz: get: operationId: healthCheck tags: [health] summary: Health check description: Returns server health status responses: "200": description: Healthy content: application/json: schema: $ref: "#/components/schemas/HealthStatus" /dashboard: get: operationId: getDashboard tags: [dashboard] summary: Dashboard summary description: Aggregated statistics across all scans. responses: "200": description: Dashboard summary content: application/json: schema: $ref: "#/components/schemas/DashboardSummary" /scans: get: operationId: listScans tags: [scans] summary: List scan history responses: "200": description: List of scans (most recent first) content: application/json: schema: type: array items: $ref: "#/components/schemas/Scan" post: operationId: createScan tags: [scans] summary: Upload a skill and run an audit description: >- Accepts a skill as a base64 ZIP archive, a single base64 file, or pasted text, runs the static rule engine and (optionally) the configured AI analysis, and returns the completed report. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/SkillScanInput" responses: "201": description: Completed scan report content: application/json: schema: $ref: "#/components/schemas/ScanDetail" "400": description: Invalid input content: application/json: schema: $ref: "#/components/schemas/ApiError" /scans/{id}/compare/{otherId}: get: operationId: compareScans tags: [scans] summary: Compare two scans on the file level description: >- Returns a file-level diff between the current scan (id) and a previously stored scan (otherId), including line-by-line diffs for modified text files. parameters: - name: id in: path required: true schema: type: integer - name: otherId in: path required: true schema: type: integer responses: "200": description: File-level comparison content: application/json: schema: $ref: "#/components/schemas/ScanComparison" "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ApiError" /scans/{id}/lineage: get: operationId: getScanLineage tags: [scans] summary: Get the version timeline for a skill family description: >- Returns every scan in the same fingerprint lineage as the given scan (linked by an identical fingerprint or by the comparedScanId chain), newest first, so the full version history of a skill can be shown on a timeline without re-scanning. parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Version timeline (most recent first) content: application/json: schema: type: array items: $ref: "#/components/schemas/ScanLineageEntry" "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ApiError" /scans/{id}: get: operationId: getScan tags: [scans] summary: Get a scan report with findings parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Scan report content: application/json: schema: $ref: "#/components/schemas/ScanDetail" "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ApiError" delete: operationId: deleteScan tags: [scans] summary: Delete a scan report parameters: - name: id in: path required: true schema: type: integer responses: "204": description: Deleted /scans/{id}/description: post: operationId: generateScanDescription tags: [scans] summary: Generate the AI description for an existing scan parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Scan report with the newly generated description content: application/json: schema: $ref: "#/components/schemas/ScanDetail" "404": description: Not found content: application/json: schema: $ref: "#/components/schemas/ApiError" "422": description: Description could not be generated content: application/json: schema: $ref: "#/components/schemas/ApiError" /providers: get: operationId: listProviders tags: [providers] summary: List configured AI providers responses: "200": description: List of providers (tokens masked) content: application/json: schema: type: array items: $ref: "#/components/schemas/AiProvider" post: operationId: createProvider tags: [providers] summary: Create an AI provider requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AiProviderInput" responses: "201": description: Created provider content: application/json: schema: $ref: "#/components/schemas/AiProvider" /providers/{id}: patch: operationId: updateProvider tags: [providers] summary: Update an AI provider parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/AiProviderUpdate" responses: "200": description: Updated provider content: application/json: schema: $ref: "#/components/schemas/AiProvider" delete: operationId: deleteProvider tags: [providers] summary: Delete an AI provider parameters: - name: id in: path required: true schema: type: integer responses: "204": description: Deleted /providers/{id}/test: post: operationId: testProvider tags: [providers] summary: Test the connection to an AI provider parameters: - name: id in: path required: true schema: type: integer responses: "200": description: Test result content: application/json: schema: $ref: "#/components/schemas/ProviderTestResult" /providers/test-connection: post: operationId: testProviderConnection tags: [providers] summary: Test a provider connection with ad-hoc configuration requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ProviderTestConnectionInput" responses: "200": description: Test result content: application/json: schema: $ref: "#/components/schemas/ProviderTestResult" /providers/list-models: post: operationId: listProviderModels tags: [providers] summary: List the available models for a provider configuration description: >- Queries the provider's models endpoint with the supplied ad-hoc configuration (or the stored token of providerId when the token is omitted) and returns the discovered model IDs. requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/ProviderListModelsInput" responses: "200": description: Discovered models content: application/json: schema: $ref: "#/components/schemas/ProviderModelsResult" /prompts: get: operationId: listPrompts tags: [prompts] summary: List configurable AI prompts responses: "200": description: List of prompts content: application/json: schema: type: array items: $ref: "#/components/schemas/Prompt" /prompts/{id}: patch: operationId: updatePrompt tags: [prompts] summary: Update an AI prompt parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/PromptUpdate" responses: "200": description: Updated prompt content: application/json: schema: $ref: "#/components/schemas/Prompt" /rules: get: operationId: listRules tags: [rules] summary: List the static rule catalog responses: "200": description: List of rules content: application/json: schema: type: array items: $ref: "#/components/schemas/Rule" /rules/{id}: patch: operationId: updateRule tags: [rules] summary: Update a rule's severity or enabled state parameters: - name: id in: path required: true schema: type: integer requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/RuleUpdate" responses: "200": description: Updated rule content: application/json: schema: $ref: "#/components/schemas/Rule" components: schemas: HealthStatus: type: object properties: status: type: string required: - status ApiError: type: object required: [error] properties: error: type: string SkillScanInput: type: object required: [source, useAi] properties: name: type: ["string", "null"] description: Optional display name for the scan source: type: string enum: [zip, file, text] useAi: type: boolean description: Whether to also run the configured AI analysis contentBase64: type: ["string", "null"] description: Base64 content for source=zip or source=file filename: type: ["string", "null"] description: Original filename for source=file or source=zip text: type: ["string", "null"] description: Raw skill text for source=text Scan: type: object required: - id - name - source - status - verdict - riskScore - fileCount - aiUsed - findingCounts - fingerprint - relation - similarity - comparedScanId - createdAt properties: id: type: integer name: type: string description: type: ["string", "null"] description: AI-generated summary of the skill's purpose (null when no AI description is available) source: type: string enum: [zip, file, text] status: type: string enum: [completed, failed] verdict: type: string enum: [pass, review, block] riskScore: type: integer fileCount: type: integer aiUsed: type: boolean aiError: type: ["string", "null"] findingCounts: $ref: "#/components/schemas/FindingCounts" fingerprint: type: string description: Deterministic hash over all files (path + per-file hash) relation: type: ["string", "null"] enum: [new, identical, modified, null] description: Relation to previously stored skills similarity: type: ["integer", "null"] description: Content-aware similarity (0-100) to the compared skill (identical files count fully, changed text files use line-level similarity) comparedScanId: type: ["integer", "null"] description: The scan this one was compared against, if any createdAt: type: string FindingCounts: type: object required: [critical, high, medium, low, info, security, privacy, total] properties: critical: type: integer high: type: integer medium: type: integer low: type: integer info: type: integer security: type: integer privacy: type: integer total: type: integer ScanCheckpoint: type: object description: >- A single inspection step (Prüfschritt) with its partial assessment (Teilbewertung). required: [id, label, category, status, findingCount, scoreDelta] properties: id: type: string label: type: string category: type: string axis: type: ["string", "null"] enum: [security, privacy, null] severity: type: ["string", "null"] enum: [critical, high, medium, low, info, null] status: type: string enum: [pass, flagged, skipped, error] findingCount: type: integer scoreDelta: type: integer detectedBy: type: ["string", "null"] enum: [static, ai, null] ScanFile: type: object required: [path, kind, size, hash, hasContent] properties: path: type: string kind: type: string enum: [instruction, script, resource] language: type: ["string", "null"] size: type: integer hash: type: string description: SHA-256 hash of the file content hasContent: type: boolean description: Whether the text content was stored (false for binary files) content: type: ["string", "null"] description: The stored text content of the file, or null for binary files Finding: type: object required: - id - ruleId - axis - severity - title - description - detectedBy properties: id: type: integer ruleId: type: string axis: type: string enum: [security, privacy] severity: type: string enum: [critical, high, medium, low, info] title: type: string description: type: string remediation: type: ["string", "null"] file: type: ["string", "null"] line: type: ["integer", "null"] snippet: type: ["string", "null"] detectedBy: type: string enum: [static, ai] ScanDetail: allOf: - $ref: "#/components/schemas/Scan" - type: object required: [files, findings, checkpoints, checkCount, comparedScan] properties: files: type: array items: $ref: "#/components/schemas/ScanFile" findings: type: array items: $ref: "#/components/schemas/Finding" checkpoints: type: array items: $ref: "#/components/schemas/ScanCheckpoint" checkCount: type: integer description: How often a skill with this exact fingerprint was scanned comparedScan: oneOf: - $ref: "#/components/schemas/ComparedScan" - type: "null" ComparedScan: type: object required: [id, name, verdict, riskScore, createdAt] properties: id: type: integer name: type: string verdict: type: string enum: [pass, review, block] riskScore: type: integer createdAt: type: string ScanLineageEntry: type: object required: - id - name - verdict - riskScore - relation - similarity - comparedScanId - fingerprint - createdAt properties: id: type: integer name: type: string verdict: type: string enum: [pass, review, block] riskScore: type: integer relation: type: ["string", "null"] enum: [new, identical, modified, null] description: Relation of this version to the one it was compared against similarity: type: ["integer", "null"] description: Content-aware similarity (0-100) to its compared version comparedScanId: type: ["integer", "null"] description: The prior version this scan was compared against, if any fingerprint: type: string createdAt: type: string ScanComparisonSide: type: object required: [id, name, verdict, riskScore, fileCount, fingerprint, createdAt] properties: id: type: integer name: type: string verdict: type: string enum: [pass, review, block] riskScore: type: integer fileCount: type: integer fingerprint: type: string createdAt: type: string DiffLine: type: object required: [type, text, previousLine, currentLine] properties: type: type: string enum: [context, add, remove] text: type: string previousLine: type: ["integer", "null"] currentLine: type: ["integer", "null"] ScanFileDiff: type: object required: - path - status - previousHash - currentHash - previousSize - currentSize - previousHasContent - currentHasContent - lineDiff properties: path: type: string status: type: string enum: [unchanged, modified, added, removed] previousHash: type: ["string", "null"] currentHash: type: ["string", "null"] previousSize: type: ["integer", "null"] currentSize: type: ["integer", "null"] previousHasContent: type: ["boolean", "null"] currentHasContent: type: ["boolean", "null"] lineDiff: oneOf: - type: array items: $ref: "#/components/schemas/DiffLine" - type: "null" ScanComparison: type: object required: [current, previous, files] properties: current: $ref: "#/components/schemas/ScanComparisonSide" previous: $ref: "#/components/schemas/ScanComparisonSide" files: type: array items: $ref: "#/components/schemas/ScanFileDiff" AiProvider: type: object required: - id - name - apiType - baseUrl - model - enabled - hasToken - tokenPreview - createdAt properties: id: type: integer name: type: string apiType: type: string enum: [openai, anthropic, custom] baseUrl: type: string model: type: string enabled: type: boolean hasToken: type: boolean tokenPreview: type: string description: Masked preview of the stored token (e.g. "sk-...abcd") createdAt: type: string AiProviderInput: type: object required: [name, apiType, baseUrl, model] properties: name: type: string minLength: 1 apiType: type: string enum: [openai, anthropic, custom] baseUrl: type: string minLength: 1 model: type: string minLength: 1 apiToken: type: string enabled: type: boolean AiProviderUpdate: type: object properties: name: type: string minLength: 1 apiType: type: string enum: [openai, anthropic, custom] baseUrl: type: string minLength: 1 model: type: string minLength: 1 apiToken: type: string description: Provide to replace the stored token; omit to keep existing enabled: type: boolean ProviderTestResult: type: object required: [ok] properties: ok: type: boolean message: type: ["string", "null"] ProviderTestConnectionInput: type: object required: [apiType, baseUrl] properties: apiType: type: string enum: [openai, anthropic, custom] baseUrl: type: string minLength: 1 model: type: string description: Optional model to exercise with a full request; when omitted the test verifies credentials via the models endpoint instead apiToken: type: string description: Token to use for the test; omit or leave empty to fall back to the stored token of providerId providerId: type: integer description: When apiToken is empty, fall back to this saved provider's stored token ProviderListModelsInput: type: object required: [apiType, baseUrl] properties: apiType: type: string enum: [openai, anthropic, custom] baseUrl: type: string minLength: 1 apiToken: type: string description: Token to use for the request; omit or leave empty to fall back to the stored token of providerId providerId: type: integer description: When apiToken is empty, fall back to this saved provider's stored token ProviderModelsResult: type: object required: [ok, models] properties: ok: type: boolean models: type: array items: type: string message: type: ["string", "null"] Prompt: type: object required: [id, key, name, content, updatedAt] properties: id: type: integer key: type: string name: type: string content: type: string updatedAt: type: string PromptUpdate: type: object properties: name: type: string minLength: 1 content: type: string minLength: 1 Rule: type: object required: - id - ruleId - axis - category - title - description - severity - detectionType - enabled properties: id: type: integer ruleId: type: string axis: type: string enum: [security, privacy] category: type: string title: type: string description: type: string severity: type: string enum: [critical, high, medium, low, info] detectionType: type: string enum: [regex, heuristic, ai] enabled: type: boolean RuleUpdate: type: object properties: severity: type: string enum: [critical, high, medium, low, info] enabled: type: boolean DashboardSummary: type: object required: - totalScans - avgRiskScore - verdictCounts - severityTotals - axisTotals - recentScans - topRules properties: totalScans: type: integer avgRiskScore: type: integer verdictCounts: $ref: "#/components/schemas/VerdictCounts" severityTotals: $ref: "#/components/schemas/SeverityTotals" axisTotals: $ref: "#/components/schemas/AxisTotals" recentScans: type: array items: $ref: "#/components/schemas/Scan" topRules: type: array items: $ref: "#/components/schemas/RuleStat" VerdictCounts: type: object required: [pass, review, block] properties: pass: type: integer review: type: integer block: type: integer SeverityTotals: type: object required: [critical, high, medium, low, info] properties: critical: type: integer high: type: integer medium: type: integer low: type: integer info: type: integer AxisTotals: type: object required: [security, privacy] properties: security: type: integer privacy: type: integer RuleStat: type: object required: [ruleId, title, axis, count] properties: ruleId: type: string title: type: string axis: type: string enum: [security, privacy] count: type: integer