{
  "openapi": "3.1.0",
  "info": {
    "title": "Makersclaw Store Registry",
    "version": "1",
    "description": "Every app in the Makersclaw App Store: the package Makersclaw installs (install scope) and the words, icon and screenshots marketing uses (marketing or install scope). Every /v1 path needs a key. A path not listed here answers 401, key or not. How to read the words: /llms.txt."
  },
  "servers": [{ "url": "https://store.makersclaw.com" }],
  "security": [{ "registryKey": [] }],
  "tags": [
    { "name": "marketing", "description": "Readable with a marketing or an install key." },
    { "name": "install", "description": "Readable with an install key only." },
    { "name": "public", "description": "No key." }
  ],
  "paths": {
    "/health.json": {
      "get": {
        "tags": ["public"],
        "summary": "Which build is live",
        "security": [],
        "responses": {
          "200": { "description": "The build", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Health" } } } }
        }
      }
    },
    "/v1/apps.json": {
      "get": {
        "tags": ["marketing"],
        "summary": "Every app",
        "responses": {
          "200": { "description": "Every app in the store", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Apps" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/v1/apps/{key}.json": {
      "get": {
        "tags": ["marketing"],
        "summary": "One app",
        "parameters": [{ "$ref": "#/components/parameters/key" }],
        "responses": {
          "200": { "description": "The app", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/App" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "No such app" }
        }
      }
    },
    "/v1/apps/{key}/icon.jpg": {
      "get": {
        "tags": ["marketing"],
        "summary": "The app's icon, 160 × 160",
        "parameters": [{ "$ref": "#/components/parameters/key" }],
        "responses": {
          "200": { "description": "The icon", "content": { "image/jpeg": {} } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "No such app, or no icon" }
        }
      }
    },
    "/v1/apps/{key}/page.md": {
      "get": {
        "tags": ["marketing"],
        "summary": "The app's full store page",
        "parameters": [{ "$ref": "#/components/parameters/key" }],
        "responses": {
          "200": { "description": "Markdown", "content": { "text/markdown": {} } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "No such app, or no page" }
        }
      }
    },
    "/v1/apps/{key}/screens/{name}.png": {
      "get": {
        "tags": ["marketing"],
        "summary": "A screenshot the app lists",
        "parameters": [{ "$ref": "#/components/parameters/key" }, { "$ref": "#/components/parameters/name" }],
        "responses": {
          "200": { "description": "PNG, a 2x picture", "content": { "image/png": {} } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Not a screenshot the app lists" }
        }
      }
    },
    "/v1/apps/{key}/previews/{name}.html": {
      "get": {
        "tags": ["marketing"],
        "summary": "A screen as static HTML",
        "description": "Sent with a sandboxing Content-Security-Policy: no scripts, only inline styles, Google Fonts and data: images. Draw it sandboxed.",
        "parameters": [{ "$ref": "#/components/parameters/key" }, { "$ref": "#/components/parameters/name" }],
        "responses": {
          "200": { "description": "HTML", "content": { "text/html": {} } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Not a preview the app lists" }
        }
      }
    },
    "/v1/index.json": {
      "get": {
        "tags": ["install"],
        "summary": "Every package version this build serves",
        "responses": {
          "200": { "description": "The index", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Index" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/v1/packages/{key}/{version}.json": {
      "get": {
        "tags": ["install"],
        "summary": "One package, as Makersclaw installs it",
        "parameters": [
          { "$ref": "#/components/parameters/key" },
          { "name": "version", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" } }
        ],
        "responses": {
          "200": { "description": "The package", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Package" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "description": "Not in this build" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "registryKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "rk_<id>_<40 hex>",
        "description": "One scope per key: marketing reads /v1/apps…; install reads everything under /v1."
      }
    },
    "parameters": {
      "key": { "name": "key", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-z0-9][a-z0-9-]{1,39}$" } },
      "name": { "name": "name", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]{0,63}$" } }
    },
    "responses": {
      "Unauthorized": {
        "description": "No key, a wrong or expired key, or a path this API does not name",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "The key's scope does not cover this path",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": { "error": { "type": "string", "enum": ["unauthorized", "forbidden"] } }
      },
      "Health": {
        "type": "object",
        "required": ["commit", "built_at", "ok"],
        "additionalProperties": false,
        "properties": {
          "commit": { "type": ["string", "null"], "description": "The store commit this build is from" },
          "built_at": { "type": "string", "format": "date-time" },
          "ok": { "type": "boolean", "description": "Every package passed its checks" }
        }
      },
      "Apps": {
        "type": "object",
        "required": ["built_at", "commit", "apps"],
        "additionalProperties": false,
        "properties": {
          "built_at": { "type": "string", "format": "date-time" },
          "commit": { "type": ["string", "null"] },
          "apps": { "type": "array", "items": { "$ref": "#/components/schemas/App" } }
        }
      },
      "App": {
        "type": "object",
        "additionalProperties": false,
        "required": ["key", "name", "version", "category", "pain", "tagline", "featured", "what_it_does", "screens_list", "runs", "every_week", "connects", "example_asks", "look", "icon", "page", "screenshots", "previews"],
        "properties": {
          "key": { "type": "string" },
          "name": { "type": "string" },
          "version": { "type": "string" },
          "category": {
            "type": "object",
            "additionalProperties": false,
            "required": ["key", "label", "blurb"],
            "properties": { "key": { "type": "string" }, "label": { "type": "string" }, "blurb": { "type": "string" } }
          },
          "pain": { "type": ["string", "null"], "description": "The problem it solves, in the store's words" },
          "tagline": { "type": "string", "description": "The one line the store shows" },
          "featured": {
            "oneOf": [
              { "type": "null" },
              {
                "type": "object",
                "additionalProperties": false,
                "required": ["kicker", "headline", "body"],
                "properties": { "kicker": { "type": "string" }, "headline": { "type": "string" }, "body": { "type": "string" } }
              }
            ]
          },
          "what_it_does": { "type": "array", "items": { "type": "string" } },
          "screens_list": { "type": "array", "items": { "type": "string" }, "description": "The app's screens by name" },
          "runs": { "type": "string", "description": "When it works on its own, in words" },
          "every_week": { "type": "array", "items": { "type": "string" }, "description": "Its scheduled work, in the workspace's time zone (UTC until set)" },
          "connects": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["label", "tools", "required"],
              "properties": { "label": { "type": "string" }, "tools": { "type": "string" }, "required": { "type": "boolean" } }
            }
          },
          "example_asks": { "type": "array", "items": { "type": "string" } },
          "look": {
            "type": "object",
            "additionalProperties": false,
            "required": ["accent", "display"],
            "properties": { "accent": { "type": ["string", "null"] }, "display": { "type": ["string", "null"] } }
          },
          "icon": { "type": ["string", "null"], "description": "Path of icon.jpg" },
          "page": { "type": ["string", "null"], "description": "Path of page.md" },
          "screenshots": { "type": "array", "items": { "$ref": "#/components/schemas/Screenshot" } },
          "previews": { "type": "array", "items": { "$ref": "#/components/schemas/Preview" } }
        }
      },
      "Screenshot": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "title", "png", "width", "height", "hero", "sample_domains"],
        "properties": {
          "name": { "type": "string" },
          "title": { "type": "string" },
          "png": { "type": "string", "description": "Path of the PNG" },
          "width": { "type": "integer", "description": "The PNG's width in pixels (a 2x picture)" },
          "height": { "type": "integer", "description": "The PNG's height in pixels" },
          "hero": { "type": "boolean", "description": "Lead with this one" },
          "sample_domains": { "$ref": "#/components/schemas/SampleDomains" }
        }
      },
      "Preview": {
        "type": "object",
        "additionalProperties": false,
        "required": ["name", "title", "html", "sample_domains"],
        "properties": {
          "name": { "type": "string" },
          "title": { "type": "string" },
          "html": { "type": "string", "description": "Path of the HTML" },
          "sample_domains": { "$ref": "#/components/schemas/SampleDomains" }
        }
      },
      "SampleDomains": {
        "type": "array",
        "items": { "type": "string" },
        "description": "The third-party domains this screen's made-up sample data shows, in email addresses, links and names (reserved names like *.example and the platforms apps connect to, such as google.com or linkedin.com, left out). The people and companies are invented, but their domains may belong to real businesses. Prefer screens where this is empty, and never quote these names, emails or domains in a post."
      },
      "Index": {
        "type": "object",
        "additionalProperties": false,
        "required": ["schema", "commit", "built_at", "contract", "apps", "failed"],
        "properties": {
          "schema": { "const": 1 },
          "commit": { "type": ["string", "null"], "description": "The full store commit sha" },
          "built_at": { "type": "string", "format": "date-time" },
          "contract": {
            "type": "object",
            "additionalProperties": false,
            "required": ["claw_commit", "makersclaw_version"],
            "properties": {
              "claw_commit": { "type": "string", "description": "The claw commit the store's contract copy is from" },
              "makersclaw_version": { "type": "string", "description": "The template pin the packages were checked against" }
            }
          },
          "apps": { "type": "array", "items": { "$ref": "#/components/schemas/IndexEntry" } },
          "failed": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": ["key", "version", "errors"],
              "properties": {
                "key": { "type": "string" },
                "version": { "type": ["string", "null"] },
                "errors": { "type": "array", "items": { "type": "string" } }
              }
            }
          }
        }
      },
      "IndexEntry": {
        "type": "object",
        "additionalProperties": false,
        "required": ["key", "version", "name", "category", "sha256", "bytes", "changes", "package"],
        "properties": {
          "key": { "type": "string" },
          "version": { "type": "string" },
          "name": { "type": "string" },
          "category": { "type": "string" },
          "sha256": { "type": "string", "description": "sha256 (hex) of the UTF-8 of canonical({name, category, card, seeds, blanks, body, files}), canonical being JSON.stringify with every object's keys sorted" },
          "bytes": { "type": "integer", "description": "Size of the package file" },
          "changes": { "type": ["string", "null"], "description": "card.changes: what this version changed, for members" },
          "package": { "type": "string", "description": "Path of the package" }
        }
      },
      "Package": {
        "type": "object",
        "additionalProperties": false,
        "required": ["key", "version", "name", "category", "card", "seeds", "blanks", "body", "files", "attribution"],
        "properties": {
          "key": { "type": "string" },
          "version": { "type": "string" },
          "name": { "type": "string" },
          "category": { "type": "string" },
          "card": { "type": "object" },
          "seeds": { "type": "object" },
          "blanks": { "type": "array" },
          "body": { "type": "string" },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["path", "content"],
              "properties": { "path": { "type": "string" }, "content": { "type": "string" } }
            }
          },
          "attribution": {
            "type": "object",
            "required": ["license", "operators", "published_from"],
            "properties": {
              "license": { "type": "string" },
              "operators": { "type": "array" },
              "published_from": {
                "type": "object",
                "required": ["source", "commit", "at"],
                "properties": {
                  "source": { "const": "makersclaw-store" },
                  "commit": { "type": "string", "description": "The full store commit sha" },
                  "at": { "type": "string", "format": "date-time", "description": "The commit's time, so a rebuild is byte-stable" }
                }
              }
            }
          }
        }
      }
    }
  }
}
