{
  "openapi": "3.1.0",
  "info": {
    "title": "SkillSafe API",
    "version": "1.0.0",
    "description": "The SkillSafe secured skill registry API. Scan, save, share, install, and verify AI skills with dual-side cryptographic verification.\n\nBase URL: `https://api.skillsafe.ai`\n\n## Authentication\n\nTwo methods are supported:\n- **Bearer token** (CLI / programmatic): `Authorization: Bearer <api_key>`\n- **Session cookie** (browser): `ss_session` HttpOnly cookie set by the auth endpoints\n\nAPI keys are obtained via the CLI device flow (`POST /v1/auth/cli`) or Google/GitHub Sign-In.\n\n## Response Format\n\nAll responses follow the envelope format:\n```json\n{ \"ok\": true, \"data\": { ... }, \"meta\": { \"request_id\": \"...\" } }\n```\nErrors: `{ \"ok\": false, \"error\": { \"code\": \"...\", \"message\": \"...\" } }`\n\n## Rate Limits\n\nAll responses include `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `X-RateLimit-Window` headers. Returns 429 with `Retry-After` when exceeded.\n\n| Path pattern | Limit |\n|---|---|\n| `/v1/auth/*` | 10 req/min |\n| `/v1/skills/*/verify`, import/discover | 10 req/min |\n| `/v1/billing/*` | 30 req/min |\n| Search, share, save, downloads | 60 req/min |\n| All other `/v1/*` | 120 req/min |",
    "contact": {
      "name": "SkillSafe",
      "url": "https://skillsafe.ai",
      "email": "hello@skillsafe.ai"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://skillsafe.ai/terms-of-service"
    }
  },
  "servers": [
    {
      "url": "https://api.skillsafe.ai",
      "description": "Production"
    }
  ],
  "security": [
    { "bearerAuth": [] },
    { "cookieAuth": [] }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key obtained from the CLI auth flow or account settings."
      },
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "ss_session",
        "description": "HttpOnly session cookie set by Google/GitHub Sign-In or key-exchange."
      }
    },
    "schemas": {
      "ApiResponse": {
        "type": "object",
        "required": ["ok"],
        "properties": {
          "ok": { "type": "boolean" },
          "data": {},
          "error": { "$ref": "#/components/schemas/ApiError" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Meta": {
        "type": "object",
        "properties": {
          "request_id": { "type": "string" },
          "pagination": { "$ref": "#/components/schemas/Pagination" }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "has_more": { "type": "boolean" },
          "next_cursor": { "type": ["string", "null"] },
          "total_count": { "type": "integer" },
          "page": { "type": "integer" },
          "per_page": { "type": "integer" },
          "total_pages": { "type": "integer" }
        }
      },
      "ApiError": {
        "type": "object",
        "required": ["code", "message"],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "unauthorized", "forbidden", "not_found", "conflict",
              "validation_error", "invalid_request", "rate_limited",
              "email_not_verified", "scan_required", "storage_limit_exceeded",
              "key_limit_exceeded", "internal_error"
            ]
          },
          "message": { "type": "string" }
        }
      },
      "Skill": {
        "type": "object",
        "properties": {
          "skill_id": { "type": "string" },
          "namespace": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string" },
          "category": { "type": "string" },
          "tags": { "type": "array", "items": { "type": "string" } },
          "visibility": { "type": "string", "enum": ["private", "public"] },
          "current_version": { "type": "string" },
          "download_count": { "type": "integer" },
          "star_count": { "type": "integer" },
          "verification_count": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "SkillVersion": {
        "type": "object",
        "properties": {
          "version_id": { "type": "string" },
          "version": { "type": "string" },
          "changelog": { "type": "string" },
          "tree_hash": { "type": "string", "description": "SHA-256 of the skill archive bytes." },
          "yanked": { "type": "boolean" },
          "saved_at": { "type": "string", "format": "date-time" },
          "scan_grade": {
            "type": "string",
            "enum": ["A", "B", "C", "D", "E", "F"],
            "description": "Security grade from the sharer's scan report, if any."
          }
        }
      },
      "ShareLink": {
        "type": "object",
        "properties": {
          "share_id": { "type": "string" },
          "skill_id": { "type": "string" },
          "version": { "type": "string" },
          "visibility": { "type": "string", "enum": ["private", "public"] },
          "expires_at": { "type": ["string", "null"], "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" },
          "download_count": { "type": "integer" }
        }
      },
      "ScanReport": {
        "type": "object",
        "description": "Security scan report produced by the SkillSafe CLI scanner (schema v1.2).",
        "properties": {
          "schema_version": { "type": "string", "enum": ["1.0", "1.1", "1.2"], "description": "Report schema version. v1.2 adds classification and advisory_count." },
          "grade": { "type": "string", "enum": ["A+", "A", "B", "C", "D", "E", "F"] },
          "score": { "type": "integer", "minimum": 0, "maximum": 100, "description": "Safety score (0–100). Only threat findings affect the score." },
          "clean": { "type": "boolean", "description": "True when threat count is 0. Advisory findings do not affect clean status." },
          "findings_count": { "type": "integer", "description": "Number of threat findings (excludes advisories)." },
          "advisory_count": { "type": "integer", "description": "Number of advisory findings (v1.2+). Advisory findings appear in documentation contexts and carry 0 score penalty." },
          "findings": {
            "type": "array",
            "description": "All findings including both threats and advisories.",
            "items": {
              "type": "object",
              "properties": {
                "severity": { "type": "string", "enum": ["critical", "high", "medium", "low", "info"] },
                "classification": { "type": "string", "enum": ["threat", "advisory"], "description": "v1.2+. Threat findings affect the score; advisory findings are informational (0 penalty)." },
                "rule": { "type": "string" },
                "message": { "type": "string" },
                "file": { "type": "string" },
                "line": { "type": "integer" }
              }
            }
          }
        }
      },
      "VerificationVerdict": {
        "type": "string",
        "enum": ["verified", "divergent", "critical"],
        "description": "verified = scans match; divergent = scans disagree; critical = tree hash mismatch (possible tampering)."
      },
      "Account": {
        "type": "object",
        "properties": {
          "account_id": { "type": "string" },
          "namespace": { "type": "string" },
          "email": { "type": "string" },
          "email_verified": { "type": "boolean" },
          "tier": { "type": "string", "enum": ["free", "pro", "enterprise"] },
          "avatar_url": { "type": "string" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      },
      "Organization": {
        "type": "object",
        "properties": {
          "org_id": { "type": "string" },
          "namespace": { "type": "string" },
          "display_name": { "type": "string" },
          "description": { "type": "string" },
          "tier": { "type": "string", "enum": ["free", "pro", "enterprise"] },
          "member_count": { "type": "integer" },
          "skill_count": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid authentication.",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
              "example": { "ok": false, "error": { "code": "unauthorized", "message": "Authentication required" } }
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ApiResponse" }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" }, "description": "Seconds until limit resets." }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ApiResponse" }
          }
        }
      }
    },
    "parameters": {
      "ns": {
        "name": "ns",
        "in": "path",
        "required": true,
        "description": "Namespace (username or org slug). May include leading `@`.",
        "schema": { "type": "string", "example": "@alice" }
      },
      "name": {
        "name": "name",
        "in": "path",
        "required": true,
        "description": "Skill name (lowercase alphanumeric + hyphens).",
        "schema": { "type": "string", "example": "my-skill" }
      },
      "version": {
        "name": "version",
        "in": "path",
        "required": true,
        "description": "Semantic version string.",
        "schema": { "type": "string", "example": "1.2.3" }
      }
    }
  },
  "tags": [
    { "name": "Skills", "description": "Save, retrieve, and manage skills and versions." },
    { "name": "Search", "description": "Search and discover public skills." },
    { "name": "Share", "description": "Create and manage share links." },
    { "name": "Verification", "description": "Submit consumer verification reports." },
    { "name": "Authentication", "description": "Sign in via Google, GitHub, email, or CLI device flow." },
    { "name": "Account", "description": "Account settings, API keys, and usage." },
    { "name": "Organizations", "description": "Create and manage organizations and members." },
    { "name": "Billing", "description": "Stripe billing, subscriptions, and invoices." },
    { "name": "Platform", "description": "Health checks and platform-level stats." },
    { "name": "Apps", "description": "Hosted mini-apps and the App Creator assistant." }
  ],
  "paths": {
    "/v1/health": {
      "get": {
        "tags": ["Platform"],
        "summary": "Health check",
        "description": "Returns system health status including D1 and R2 connectivity.",
        "security": [],
        "responses": {
          "200": {
            "description": "System is healthy.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiResponse" },
                "example": { "ok": true, "data": { "status": "ok", "db": "ok", "storage": "ok" } }
              }
            }
          }
        }
      }
    },
    "/v1/stats": {
      "get": {
        "tags": ["Platform"],
        "summary": "Platform statistics",
        "description": "Public aggregate metrics: total skills, publishers, and scan reports.",
        "security": [],
        "responses": {
          "200": {
            "description": "Platform stats.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiResponse" },
                "example": { "ok": true, "data": { "skills": 1200, "publishers": 340, "scan_reports": 4800 } }
              }
            }
          }
        }
      }
    },
    "/v1/cli/version": {
      "get": {
        "tags": ["Platform"],
        "summary": "Latest CLI version",
        "security": [],
        "responses": {
          "200": {
            "description": "Latest CLI version info.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiResponse" },
                "example": { "ok": true, "data": { "version": "0.1.7", "downloads": { "npm": "https://www.npmjs.com/package/@skillsafe/cli", "install": "npm install -g @skillsafe/cli", "desktop": "https://app.skillsafe.ai/" }, "mcp": { "url": "https://api.skillsafe.ai/mcp", "transport": "streamable-http" } } }
              }
            }
          }
        }
      }
    },
    "/v1/badge/@{ns}/{name}/{variant}": {
      "get": {
        "tags": ["Platform"],
        "summary": "README status badge (SVG)",
        "description": "Returns a small SVG badge for embedding in README files or other documentation. The link target should be the skill detail page.",
        "security": [],
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "name": "variant", "in": "path", "required": true, "schema": { "type": "string", "enum": ["verified", "installs", "scan", "eval"] }, "description": "Which badge to render." }
        ],
        "responses": {
          "200": {
            "description": "SVG badge.",
            "content": { "image/svg+xml": { "schema": { "type": "string" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/blobs/{hash}": {
      "get": {
        "tags": ["Platform"],
        "summary": "Fetch a content-addressed blob",
        "description": "Returns the raw bytes of a file referenced from a skill's manifest. The hash is the SHA-256 prefix (e.g. `sha256:abc123…`). No auth needed for blobs referenced by public skills; auth required otherwise.",
        "security": [],
        "parameters": [
          { "name": "hash", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Content hash, e.g. `sha256:abc123...`." }
        ],
        "responses": {
          "200": {
            "description": "Raw file bytes.",
            "content": { "application/octet-stream": { "schema": { "type": "string", "format": "binary" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/auth/email/change-password": {
      "post": {
        "tags": ["Auth"],
        "summary": "Change account password",
        "description": "Authenticated. Requires the current password in the request body.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["current_password", "new_password"],
                "properties": {
                  "current_password": { "type": "string" },
                  "new_password": { "type": "string", "minLength": 8 }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Password changed." },
          "400": { "description": "Validation error or missing required fields." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/account/claim": {
      "post": {
        "tags": ["Account"],
        "summary": "Claim a publisher account via email token",
        "description": "Verifies a one-time claim token and sets a password on the account. Both `token` and `password` are required.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["token", "password"],
                "properties": {
                  "token": { "type": "string" },
                  "password": { "type": "string", "minLength": 8 }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Claim accepted; account is verified." },
          "400": { "description": "Validation error or missing required fields." },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/skills/search": {
      "get": {
        "tags": ["Search"],
        "summary": "Search public skills",
        "description": "Full-text search over public skills. Supports filtering by category, namespace, and type. Paginated with cursor or page-based pagination.",
        "security": [],
        "parameters": [
          { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Search query." },
          { "name": "category", "in": "query", "schema": { "type": "string" }, "description": "Filter by category slug." },
          { "name": "namespace", "in": "query", "schema": { "type": "string" }, "description": "Filter to a specific namespace." },
          { "name": "type", "in": "query", "schema": { "type": "string", "enum": ["skill", "skillset"] } },
          {
            "name": "sort", "in": "query",
            "schema": { "type": "string", "enum": ["popular", "recent", "verified", "trending", "hot"], "default": "recent" }
          },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" }, "description": "Cursor for cursor-based pagination." },
          { "name": "page", "in": "query", "schema": { "type": "integer", "minimum": 1 }, "description": "Page number (overrides cursor)." }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of matching skills.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiResponse" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/skills/{ns}/{name}": {
      "get": {
        "tags": ["Skills"],
        "summary": "Get skill metadata",
        "description": "Returns metadata for a skill. Private skills are only visible to their owner.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" }
        ],
        "responses": {
          "200": {
            "description": "Skill metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": { "data": { "$ref": "#/components/schemas/Skill" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "post": {
        "tags": ["Skills"],
        "summary": "Save a skill version",
        "description": "Upload a new version of a skill. The skill is saved privately by default — no email verification or scan report required. If `--version` is omitted, the CLI auto-increments the patch version. If content is unchanged from the latest version, the save is skipped.\n\nRequest must be `multipart/form-data` with:\n- `archive`: zip file of the skill directory\n- `metadata`: JSON string with optional fields: `version`, `description`, `category`, `tags`, `changelog`",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["archive"],
                "properties": {
                  "archive": { "type": "string", "format": "binary", "description": "ZIP archive of the skill directory." },
                  "metadata": {
                    "type": "string",
                    "description": "JSON string: `{version?, description?, category?, tags?, changelog?}`"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Version saved (or skipped — content unchanged)." },
          "201": { "description": "New version created." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "tags": ["Skills"],
        "summary": "Delete a skill",
        "description": "Soft-deletes the skill and all its versions. Irreversible.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" }
        ],
        "responses": {
          "200": { "description": "Skill deleted." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/skills/{ns}/{name}/versions": {
      "get": {
        "tags": ["Skills"],
        "summary": "List skill versions",
        "description": "Paginated list of versions for a skill.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 20 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Version list.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/SkillVersion" } }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/skills/{ns}/{name}/versions/{version}": {
      "get": {
        "tags": ["Skills"],
        "summary": "Get version details",
        "description": "Returns details for a specific version including tree hash and scan report if available.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "$ref": "#/components/parameters/version" }
        ],
        "responses": {
          "200": {
            "description": "Version details.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": { "data": { "$ref": "#/components/schemas/SkillVersion" } }
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/skills/{ns}/{name}/download/{version}": {
      "get": {
        "tags": ["Skills"],
        "summary": "Download skill archive (owner only)",
        "description": "Downloads the ZIP archive for a specific version. Only the skill owner can download their own private skills. Share links should be used for sharing with others.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "$ref": "#/components/parameters/version" }
        ],
        "responses": {
          "200": {
            "description": "ZIP archive.",
            "content": { "application/zip": { "schema": { "type": "string", "format": "binary" } } }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/skills/{ns}/{name}/versions/{version}/yank": {
      "post": {
        "tags": ["Skills"],
        "summary": "Yank a version",
        "description": "Marks a version as yanked. It remains visible in version history but cannot be downloaded. Other versions are unaffected.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "$ref": "#/components/parameters/version" }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": { "reason": { "type": "string" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Version yanked." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/skills/{ns}/{name}/star": {
      "post": {
        "tags": ["Skills"],
        "summary": "Star a skill",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" }
        ],
        "responses": {
          "200": { "description": "Starred." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "delete": {
        "tags": ["Skills"],
        "summary": "Unstar a skill",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" }
        ],
        "responses": {
          "200": { "description": "Unstarred." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/skills/{ns}/{name}/current-version": {
      "post": {
        "tags": ["Skills"],
        "summary": "Set current version",
        "description": "Sets which version is shown as the default on the skill page.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["version"],
                "properties": { "version": { "type": "string" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Current version updated." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/skills/{ns}/{name}/versions/{version}/share": {
      "post": {
        "tags": ["Share"],
        "summary": "Create a share link",
        "description": "Creates a share link for a specific version. By default the link is private (link-only access). Use `visibility: 'public'` to make the skill discoverable via search.\n\n**Requires:** email verification + a scan report on the version.\n\n**Free tier limit:** max 5 shared skills.",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "$ref": "#/components/parameters/version" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "visibility": { "type": "string", "enum": ["private", "public"], "default": "private" },
                  "expires_in": { "type": "string", "enum": ["1d", "7d", "30d", "never"], "default": "never" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Share link created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": { "data": { "$ref": "#/components/schemas/ShareLink" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "description": "Email not verified or scan report required." }
        }
      }
    },
    "/v1/skills/{ns}/{name}/versions/{version}/shares": {
      "get": {
        "tags": ["Share"],
        "summary": "List share links for a version",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "$ref": "#/components/parameters/version" }
        ],
        "responses": {
          "200": {
            "description": "Share links.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/ShareLink" } }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/share/{shareId}": {
      "get": {
        "tags": ["Share"],
        "summary": "Get share link info",
        "description": "Returns metadata about a share link. No authentication required.",
        "security": [],
        "parameters": [
          { "name": "shareId", "in": "path", "required": true, "schema": { "type": "string", "description": "Share link ID (`shr_` prefix)." } }
        ],
        "responses": {
          "200": {
            "description": "Share link info.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiResponse" }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "delete": {
        "tags": ["Share"],
        "summary": "Revoke a share link",
        "description": "Permanently revokes a share link. Revoking all public share links reverts the skill's visibility to private.",
        "parameters": [
          { "name": "shareId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Share link revoked." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/share/{shareId}/download": {
      "get": {
        "tags": ["Share"],
        "summary": "Download via share link",
        "description": "Downloads the skill archive using a share link. No authentication required.",
        "security": [],
        "parameters": [
          { "name": "shareId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "ZIP archive.",
            "content": { "application/zip": { "schema": { "type": "string", "format": "binary" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "410": { "description": "Share link has expired or been revoked." },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/skills/{ns}/{name}/versions/{version}/verify": {
      "post": {
        "tags": ["Verification"],
        "summary": "Submit consumer verification report",
        "description": "After downloading and scanning a skill, submit the consumer's scan report. The server compares it against the sharer's report and returns a verdict.\n\n- `verified` — scans match, safe to use\n- `divergent` — scans disagree, review before using\n- `critical` — tree hash mismatch, possible tampering, do not use",
        "parameters": [
          { "$ref": "#/components/parameters/ns" },
          { "$ref": "#/components/parameters/name" },
          { "$ref": "#/components/parameters/version" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["scan_report"],
                "properties": {
                  "scan_report": {
                    "description": "The scan report object or JSON string produced by the CLI scanner.",
                    "oneOf": [
                      { "$ref": "#/components/schemas/ScanReport" },
                      { "type": "string" }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Verification result.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "verdict": { "$ref": "#/components/schemas/VerificationVerdict" },
                        "details": { "type": "string" }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/auth/google": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Sign in with Google",
        "description": "Authenticates with a Google ID token (from Google Sign-In). Sets an HttpOnly session cookie on success.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["credential"],
                "properties": { "credential": { "type": "string", "description": "Google ID token from GSI." } }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Authenticated. Session cookie set.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiResponse" }
              }
            }
          }
        }
      }
    },
    "/v1/auth/github": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Sign in with GitHub",
        "description": "Authenticates with a GitHub OAuth code. Sets an HttpOnly session cookie on success.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["code"],
                "properties": {
                  "code": { "type": "string", "description": "GitHub OAuth authorization code." },
                  "redirect_uri": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Authenticated. Session cookie set." }
        }
      }
    },
    "/v1/auth/cli": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Create CLI auth session",
        "description": "Creates a device-flow auth session. The caller opens the returned `login_url` in the user's browser, then polls `GET /v1/auth/cli/{session_id}` until it receives an API key (one-time read; keys minted this way last 1 year). Optional body: `{\"label\"}` names the minted key.",
        "security": [],
        "responses": {
          "201": {
            "description": "Session created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "session_id": { "type": "string" },
                        "login_url": { "type": "string", "description": "URL to open in the browser for approval." },
                        "expires_in": { "type": "integer", "description": "Session lifetime in seconds (default 900)." }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/cli/{session_id}": {
      "get": {
        "tags": ["Authentication"],
        "summary": "Poll CLI auth session",
        "description": "Polls for CLI auth session approval. Returns `pending` until the user approves in the browser, then returns the API key.",
        "security": [],
        "parameters": [
          { "name": "session_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Session status.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": { "type": "string", "enum": ["pending", "approved", "expired"] },
                        "api_key": { "type": "string", "description": "Present only when status = approved." }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/cli/{session_id}/approve": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Approve CLI auth session",
        "description": "Browser-side action: approves the CLI session and issues an API key. Called by the `/auth/cli` web page after the user signs in.",
        "parameters": [
          { "name": "session_id", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Session approved." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/auth/email/send-code": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Send email sign-in code",
        "description": "Sends a 6-digit one-time code to the provided email address.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": { "email": { "type": "string", "format": "email" } }
              }
            }
          }
        },
        "responses": { "200": { "description": "Code sent." } }
      }
    },
    "/v1/auth/email/verify-code": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Verify email sign-in code",
        "description": "Verifies the 6-digit code and creates a session.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email", "code"],
                "properties": {
                  "email": { "type": "string", "format": "email" },
                  "code": { "type": "string", "minLength": 6, "maxLength": 6 }
                }
              }
            }
          }
        },
        "responses": { "200": { "description": "Authenticated." } }
      }
    },
    "/v1/auth/logout": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Log out",
        "description": "Revokes the current session API key and clears the session cookie.",
        "responses": { "200": { "description": "Logged out." } }
      }
    },
    "/v1/auth/key-exchange": {
      "post": {
        "tags": ["Authentication"],
        "summary": "Exchange Bearer token for session cookie",
        "description": "Used by the web frontend after a CLI sign-in to exchange a Bearer token for an HttpOnly session cookie.",
        "responses": { "200": { "description": "Cookie set." } }
      }
    },
    "/v1/account": {
      "get": {
        "tags": ["Account"],
        "summary": "Get account details",
        "description": "Returns the authenticated account's profile, tier, email verification status, and settings.",
        "responses": {
          "200": {
            "description": "Account details.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": { "data": { "$ref": "#/components/schemas/Account" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "delete": {
        "tags": ["Account"],
        "summary": "Schedule account deletion",
        "description": "Schedules the account for deletion. Requires email verification.",
        "responses": {
          "200": { "description": "Deletion scheduled." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/account/verify-email": {
      "post": {
        "tags": ["Account"],
        "summary": "Send verification email",
        "description": "Sends an email verification link to the account's email address.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": { "email": { "type": "string", "format": "email" } }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Verification email sent." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/account/usage": {
      "get": {
        "tags": ["Account"],
        "summary": "Get usage stats",
        "description": "Returns storage usage, skill counts, and API key count.",
        "responses": {
          "200": { "description": "Usage stats." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/account/keys": {
      "get": {
        "tags": ["Account"],
        "summary": "List API keys",
        "description": "Returns all active (non-revoked) API keys for the account. Max 20 per account.",
        "responses": {
          "200": { "description": "API key list." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "post": {
        "tags": ["Account"],
        "summary": "Create API key",
        "description": "Creates a new API key. The raw key is returned once — store it securely. Keys expire after 7 days by default; pass expires_in_days for a different lifetime, or null for a key that never expires.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": { "type": "string", "description": "Human-readable label for the key." },
                  "expires_in_days": {
                    "type": ["integer", "null"],
                    "minimum": 1,
                    "maximum": 3650,
                    "default": 7,
                    "description": "Key lifetime in days (1–3650). null = never expires. Defaults to 7."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "key_id": { "type": "string" },
                        "api_key": { "type": "string", "description": "Raw key — shown once." },
                        "expires_at": { "type": ["string", "null"], "format": "date-time", "description": "Expiry timestamp; null if the key never expires." }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/account/keys/{keyId}": {
      "delete": {
        "tags": ["Account"],
        "summary": "Revoke API key",
        "parameters": [
          { "name": "keyId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Key revoked." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/orgs": {
      "get": {
        "tags": ["Organizations"],
        "summary": "List organizations",
        "description": "Returns organizations the authenticated user belongs to.",
        "responses": {
          "200": {
            "description": "Organization list.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": { "type": "array", "items": { "$ref": "#/components/schemas/Organization" } }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": ["Organizations"],
        "summary": "Create organization",
        "description": "Creates a new organization. Requires email verification.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["namespace", "display_name"],
                "properties": {
                  "namespace": { "type": "string", "description": "Unique org slug (lowercase alphanumeric + hyphens)." },
                  "display_name": { "type": "string" },
                  "description": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Organization created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": { "data": { "$ref": "#/components/schemas/Organization" } }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "description": "Email not verified." },
          "409": { "description": "Namespace already taken." }
        }
      }
    },
    "/v1/orgs/{orgId}": {
      "get": {
        "tags": ["Organizations"],
        "summary": "Get organization details",
        "description": "Members see full details. Non-members see public info only.",
        "parameters": [
          { "name": "orgId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Organization details.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": { "data": { "$ref": "#/components/schemas/Organization" } }
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": ["Organizations"],
        "summary": "Update organization",
        "description": "Updates organization display_name, description, or avatar. Owner only.",
        "parameters": [
          { "name": "orgId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "display_name": { "type": "string" },
                  "description": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      },
      "delete": {
        "tags": ["Organizations"],
        "summary": "Delete organization",
        "description": "Deletes the organization. Owner only. All org skills are deleted.",
        "parameters": [
          { "name": "orgId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Deleted." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/orgs/{orgId}/members": {
      "get": {
        "tags": ["Organizations"],
        "summary": "List organization members",
        "parameters": [
          { "name": "orgId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Member list." }
        }
      }
    },
    "/v1/orgs/{orgId}/members/invite": {
      "post": {
        "tags": ["Organizations"],
        "summary": "Invite a member",
        "description": "Sends an invitation by email. Owner or admin only.",
        "parameters": [
          { "name": "orgId", "in": "path", "required": true, "schema": { "type": "string" } }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["email"],
                "properties": {
                  "email": { "type": "string", "format": "email" },
                  "role": { "type": "string", "enum": ["admin", "member"], "default": "member" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Invitation sent." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "tags": ["Billing"],
        "summary": "Create checkout session",
        "description": "Creates a Stripe checkout session for upgrading to a paid plan. Requires email verification.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["price_id"],
                "properties": {
                  "price_id": { "type": "string", "description": "Stripe Price ID." },
                  "success_url": { "type": "string" },
                  "cancel_url": { "type": "string" }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout session URL.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": { "url": { "type": "string" } }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/subscription": {
      "get": {
        "tags": ["Billing"],
        "summary": "Get subscription details",
        "description": "Returns current Stripe subscription status, plan, and renewal date.",
        "responses": {
          "200": { "description": "Subscription details." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/billing/invoices": {
      "get": {
        "tags": ["Billing"],
        "summary": "List invoices",
        "parameters": [
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 10 } },
          { "name": "cursor", "in": "query", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Invoice list." },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/users/{username}": {
      "get": {
        "tags": ["Account"],
        "summary": "Get public user profile",
        "description": "Returns public profile and aggregated stats for a user. No authentication required.",
        "security": [],
        "parameters": [
          {
            "name": "username",
            "in": "path",
            "required": true,
            "description": "Username or namespace (leading @ is stripped automatically).",
            "schema": { "type": "string", "example": "alice" }
          }
        ],
        "responses": {
          "200": {
            "description": "Public user profile.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [{ "$ref": "#/components/schemas/ApiResponse" }],
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "username": { "type": "string", "example": "alice" },
                        "username_display": { "type": "string", "example": "Alice" },
                        "namespace": { "type": "string", "example": "@alice" },
                        "avatar_url": { "type": "string", "nullable": true },
                        "created_at": { "type": "string", "format": "date-time" },
                        "public_skill_count": { "type": "integer", "example": 12 },
                        "total_installs": { "type": "integer", "example": 4200 },
                        "total_stars": { "type": "integer", "example": 310 },
                        "total_verifications": { "type": "integer", "example": 89 }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/v1/creator/chat": {
      "post": {
        "tags": ["Apps"],
        "summary": "App Creator assistant turn (SSE)",
        "description": "Run one App Creator turn: the assistant builds and modifies hosted mini-apps (at https://{slug}.skillsafe.ai) for the authenticated account by calling server-side tools. Conversation state is client-held: send the full user/assistant transcript each turn. Responds with a Server-Sent Events stream. Auth: a creator-scoped app-user session minted by signing in at https://creator.skillsafe.ai/ (browser feature) — API keys get 401; agents drive the platform API directly (https://deploy.skillsafe.ai/llms.txt). Powered by Workers AI gemma. Free; 50 messages/account/day; one turn in flight per account.",
        "security": [{ "bearerAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["messages"],
                "properties": {
                  "messages": {
                    "type": "array",
                    "maxItems": 40,
                    "description": "Full conversation so far, oldest first; the last message must be from the user.",
                    "items": {
                      "type": "object",
                      "required": ["role", "content"],
                      "properties": {
                        "role": { "type": "string", "enum": ["user", "assistant"] },
                        "content": { "type": "string", "maxLength": 16384 }
                      }
                    }
                  },
                  "confirmations": {
                    "type": "array",
                    "maxItems": 10,
                    "items": { "type": "string", "maxLength": 100 },
                    "description": "Consent strings for sensitive actions, echoed from a confirm_request event (e.g. \"publish_app:my-slug\")."
                  },
                  "attachments": {
                    "type": "array",
                    "maxItems": 1,
                    "description": "Pasted artifact to rebuild as an app (max 100 KB). Read by tools only; never enters the model transcript.",
                    "items": {
                      "type": "object",
                      "required": ["name", "content"],
                      "properties": {
                        "name": { "type": "string", "maxLength": 80 },
                        "content": { "type": "string" }
                      }
                    }
                  }
                }
              },
              "example": { "messages": [{ "role": "user", "content": "Build a tip calculator app" }] }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SSE stream (text/event-stream). Events: text {delta}, tool_start {id, name, args_preview}, tool_result {id, name, ok, summary, url?, findings?}, confirm_request {action, key, slug, title}, error {code, message}, done {stop, tools_used}, ping (heartbeat every 15s).",
            "content": { "text/event-stream": { "schema": { "type": "string" } } }
          },
          "401": { "description": "Not a creator browser session — API keys and missing/invalid tokens are rejected (agents: use the platform API directly)." },
          "403": { "description": "Session not valid for the App Creator (wrong app, guest subject, missing creator scope, or email not verified)." },
          "409": { "description": "A previous turn for this account is still in flight." },
          "413": { "description": "Body exceeds 256 KB, or an attachment exceeds 100 KB." },
          "429": { "description": "Daily message cap (50/day) or per-IP rate limit reached." },
          "503": { "description": "Creator disabled/unconfigured, or no assistant backend available." }
        }
      }
    },
    "/v1/creator/me": {
      "get": {
        "tags": ["Apps"],
        "summary": "Creator bootstrap: username and owned apps",
        "description": "The caller's username and apps — the agent entry point's who-am-I endpoint. Agents deploy apps via the platform API directly (see https://deploy.skillsafe.ai/llms.txt).",
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "Username + apps.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ApiResponse" },
                "example": {
                  "ok": true,
                  "data": {
                    "username": "alice",
                    "daily": { "used": 3, "cap": 50 },
                    "apps": [{ "slug": "tipcalc", "title": "Tip Calculator", "visibility": "public", "status": "active", "has_release": true, "url": "https://tipcalc.skillsafe.ai/" }]
                  }
                }
              }
            }
          },
          "401": { "description": "Missing or invalid credentials." },
          "403": { "description": "The session token lacks creator access (wrong app, guest subject, or missing creator scope)." }
        }
      }
    }
  }
}
