{
  "openapi": "3.1.0",
  "info": {
    "title": "schematize API",
    "version": "0.1.0",
    "summary": "Store, version, replicate, and publish Claude Code skills — plus the publisher and admin/backoffice workflow.",
    "description": "The **schematize platform API** (`api.schematize.org`) is the single backend behind the\nschematize app, the skills catalog, and the `backoffice.schematize.org` admin console.\n\n## What it does\n- **Store & version your skills.** Any authenticated user can upload a skill package,\n  version it (SemVer), and keep it **private**. Nothing is public until it is reviewed.\n- **Replicate your environment.** Logged into the same account, the app's agent can pull a\n  manifest of all your skills and download them to reconstruct `~/.claude/skills`.\n- **Publish for everyone — behind two gates.** To publish at all you must hold a\n  **publisher** account (request it; an admin approves it). Then every publish *and every\n  update* of a skill goes through **manual review** before it reaches the public catalog.\n- **Administer everything (backoffice).** An admin manages users, the publisher-request\n  queue, the publication **review queue**, audits every skill (including private ones) with\n  full version history, and tracks publications across the adjacent sites (e.g. blueprint).\n\n## Roles (authorization is decided server-side, deny-by-default)\n- `user` — upload/store/version **own** skills (private), download them, replicate the\n  environment, request a publisher account.\n- `publisher` — a user approved to *submit* skills for publication. Each submission still\n  goes through review.\n- `admin` — the backoffice: users, publisher requests, review queue, audit, publications.\n\nOwnership maps to the IdP's ReBAC (`object_type: skill`, `relation: owner|publisher`);\nplatform roles to `platform:publisher` / `platform:admin`. The API enforces these on the\nserver — a token never carries a role list (it stays thin: `sub` + `aal`).\n\n## Skill lifecycle\n```\nupload (private) ──▶ versions (SemVer, checksum, changelog)\n                      └─[to publish]─▶ become a publisher ──▶ submit a version\n                                        └─▶ review queue ──▶ admin approve ──▶ PUBLIC (catalog)\n                                                          └─▶ admin reject  ──▶ back to author\nan update to a published skill = a new version ──▶ back through the review queue.\n```\n\n## Authentication (for the app's agent)\nThis API is a **resource server**. It validates a JWT access token issued by the house IdP\nat `auth.schematize.org` (RS256, verified via JWKS; asymmetric algorithms only — `none`/`HS*`\nare rejected). Discovery: `https://auth.schematize.org/.well-known/openid-configuration`.\nA CLI/agent obtains a token via the **OAuth 2.0 Device Authorization grant**\n(`POST /device_authorization` at the IdP) — no browser redirect, no client secret.\n\n## Non-negotiable floors (encoded in this contract)\n- Uploads are **untrusted content**: bounded size (**413** over the limit), validated\n  structure and no path traversal in the archive (**422** on a bad package).\n- **Nothing is published without a manual review** (`POST /admin/review-queue/{id}/approve`).\n- **Deny-by-default:** `401` (no/invalid token), `403` (not owner / not publisher / not admin).\n- Every admin action and state transition is written to the **audit log**.\n",
    "contact": {
      "name": "schematize",
      "url": "https://schematize.org"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "https://api.schematize.org",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "System",
      "description": "Health and readiness."
    },
    {
      "name": "Catalog",
      "description": "The public, published skills catalog (read-only, no auth)."
    },
    {
      "name": "My Skills",
      "description": "Upload, version, and download your own skills (private by default)."
    },
    {
      "name": "Environment",
      "description": "Replicate your `~/.claude/skills` on another machine."
    },
    {
      "name": "Publisher",
      "description": "Become a publisher and submit skills for publication."
    },
    {
      "name": "Social",
      "description": "Ratings, reviews, comments, profiles, follows, feed, notifications."
    },
    {
      "name": "Admin · Users",
      "description": "Backoffice — user administration (admin only)."
    },
    {
      "name": "Admin · Publisher Requests",
      "description": "Backoffice — approve/reject publisher accounts (admin only)."
    },
    {
      "name": "Admin · Review Queue",
      "description": "Backoffice — manual review of skill publications and updates (admin only)."
    },
    {
      "name": "Admin · Skills",
      "description": "Backoffice — audit every skill and version, including private ones (admin only)."
    },
    {
      "name": "Admin · Publications",
      "description": "Backoffice — track/manage publications on adjacent sites, e.g. blueprint (admin only)."
    },
    {
      "name": "Admin · Audit",
      "description": "Backoffice — the audit log of every admin action and state transition (admin only)."
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Liveness",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/ready": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Readiness (DB reachable)",
        "security": [],
        "responses": {
          "200": {
            "description": "Ready"
          },
          "503": {
            "description": "Not ready"
          }
        }
      }
    },
    "/skills": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "List published skills",
        "security": [],
        "parameters": [
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "top",
                "new"
              ],
              "default": "name"
            }
          },
          {
            "name": "category",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Full-text query"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillPage"
                }
              }
            }
          }
        }
      }
    },
    "/skills/top": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Top-rated published skills",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillPage"
                }
              }
            }
          }
        }
      }
    },
    "/skills/{slug}": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Get a published skill",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/skills/{slug}/versions": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "List a published skill's versions",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SkillVersion"
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/skills/{slug}/download": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Download a published skill package",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Slug"
          },
          {
            "name": "version",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "SemVer; defaults to latest"
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the versioned package (`.zip`)"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/versions": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Map of slug → latest published version",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/me/skills": {
      "get": {
        "tags": [
          "My Skills"
        ],
        "summary": "List my skills (all visibilities)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "tags": [
          "My Skills"
        ],
        "summary": "Register a new skill (owner = you, private)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SkillCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/me/skills/{slug}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "tags": [
          "My Skills"
        ],
        "summary": "Get one of my skills",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "My Skills"
        ],
        "summary": "Update my skill's metadata",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SkillUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "tags": [
          "My Skills"
        ],
        "summary": "Delete my skill (only if never published)",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/me/skills/{slug}/versions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "tags": [
          "My Skills"
        ],
        "summary": "List my skill's versions",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SkillVersion"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "My Skills"
        ],
        "summary": "Upload a new version (the skill package)",
        "description": "Upload the packaged skill as a `.zip`/`.tar.gz` (the same layout the CLI installs:\n`SKILL.md` + `references/`, `commands/`, `assets/`…). The server validates the archive\n(size, structure, no path traversal), computes a SHA-256 checksum and stores it as an\nimmutable version. Visibility stays **private** until submitted and approved.\n",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "package",
                  "version"
                ],
                "properties": {
                  "package": {
                    "type": "string",
                    "format": "binary",
                    "description": "The skill archive (.zip/.tar.gz)"
                  },
                  "version": {
                    "type": "string",
                    "description": "SemVer",
                    "e.g. 1.2.0": null
                  },
                  "changelog": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillVersion"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/me/skills/{slug}/versions/{version}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        },
        {
          "$ref": "#/components/parameters/Version"
        }
      ],
      "get": {
        "tags": [
          "My Skills"
        ],
        "summary": "Get one of my skill versions",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillVersion"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/me/skills/{slug}/versions/{version}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        },
        {
          "$ref": "#/components/parameters/Version"
        }
      ],
      "get": {
        "tags": [
          "My Skills"
        ],
        "summary": "Download my skill version's package",
        "responses": {
          "302": {
            "description": "Redirect to the package"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/me/environment": {
      "get": {
        "tags": [
          "Environment"
        ],
        "summary": "Manifest of my skills (to replicate the environment)",
        "description": "Returns every skill you own with its latest version, checksum and download URL — enough\nfor the app's agent to rebuild `~/.claude/skills` on another machine, logged into the\nsame account.\n",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EnvironmentManifest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/me/environment/download": {
      "get": {
        "tags": [
          "Environment"
        ],
        "summary": "Download my whole environment as one bundle",
        "responses": {
          "200": {
            "description": "A `.tar.gz` bundle of all your skills",
            "content": {
              "application/gzip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/me/publisher-request": {
      "get": {
        "tags": [
          "Publisher"
        ],
        "summary": "My publisher-account request status",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublisherRequest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "You have no request on file"
          }
        }
      },
      "post": {
        "tags": [
          "Publisher"
        ],
        "summary": "Request a publisher account",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublisherRequestCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Submitted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublisherRequest"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "description": "A request is already pending or you are already a publisher"
          }
        }
      }
    },
    "/me/skills/{slug}/versions/{version}/submit": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        },
        {
          "$ref": "#/components/parameters/Version"
        }
      ],
      "post": {
        "tags": [
          "Publisher"
        ],
        "summary": "Submit a version for publication (publisher only)",
        "description": "Requires the `publisher` role. Creates a submission in the review queue; the skill/version is **not** public until an admin approves it.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmissionCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Submitted for review",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Not a publisher",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already submitted / already published"
          }
        }
      }
    },
    "/me/submissions": {
      "get": {
        "tags": [
          "Publisher"
        ],
        "summary": "My publication submissions",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Submission"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/me/submissions/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "tags": [
          "Publisher"
        ],
        "summary": "Withdraw a submission (while still pending)",
        "responses": {
          "204": {
            "description": "Withdrawn"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Already decided"
          }
        }
      }
    },
    "/skills/{slug}/ratings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "tags": [
          "Social"
        ],
        "summary": "List ratings",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "put": {
        "tags": [
          "Social"
        ],
        "summary": "Set my rating (1–5)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RatingInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Social"
        ],
        "summary": "Clear my rating",
        "responses": {
          "204": {
            "description": "Cleared"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/skills/{slug}/reviews": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "tags": [
          "Social"
        ],
        "summary": "List reviews",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Social"
        ],
        "summary": "Write/replace my review",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReviewInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/skills/{slug}/comments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "tags": [
          "Social"
        ],
        "summary": "List comments (threaded)",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      },
      "post": {
        "tags": [
          "Social"
        ],
        "summary": "Post a comment",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommentInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/profiles/{handle}": {
      "parameters": [
        {
          "name": "handle",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Social"
        ],
        "summary": "Get a public profile",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profile"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/me/profile": {
      "put": {
        "tags": [
          "Social"
        ],
        "summary": "Update my profile",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "tags": [
          "Social"
        ],
        "summary": "Delete my profile (step-up AAL required)",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Step-up authentication required"
          }
        }
      }
    },
    "/me/feed": {
      "get": {
        "tags": [
          "Social"
        ],
        "summary": "My activity feed",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/me/notifications": {
      "get": {
        "tags": [
          "Social"
        ],
        "summary": "My notifications",
        "responses": {
          "200": {
            "description": "OK"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/reports": {
      "post": {
        "tags": [
          "Social"
        ],
        "summary": "Report content",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Filed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/admin/users": {
      "get": {
        "tags": [
          "Admin · Users"
        ],
        "summary": "List users",
        "parameters": [
          {
            "name": "role",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Role"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/UserStatus"
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserPage"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/users/{sub}": {
      "parameters": [
        {
          "name": "sub",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          },
          "description": "The user's opaque identity (ULID)"
        }
      ],
      "get": {
        "tags": [
          "Admin · Users"
        ],
        "summary": "Get a user",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Admin · Users"
        ],
        "summary": "Update a user (status / roles)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminUserUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/publisher-requests": {
      "get": {
        "tags": [
          "Admin · Publisher Requests"
        ],
        "summary": "The publisher-request queue",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PublisherRequestStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublisherRequest"
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/publisher-requests/{id}/approve": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "tags": [
          "Admin · Publisher Requests"
        ],
        "summary": "Approve — grant the publisher role",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublisherRequest"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/publisher-requests/{id}/reject": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "tags": [
          "Admin · Publisher Requests"
        ],
        "summary": "Reject — with a reason",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Decision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PublisherRequest"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/review-queue": {
      "get": {
        "tags": [
          "Admin · Review Queue"
        ],
        "summary": "Pending publication submissions",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/SubmissionStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Submission"
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/review-queue/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Admin · Review Queue"
        ],
        "summary": "Full submission (skill, version, package, diff to the live version)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionDetail"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/admin/review-queue/{id}/claim": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "tags": [
          "Admin · Review Queue"
        ],
        "summary": "Claim for review (→ in_review)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/review-queue/{id}/approve": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "tags": [
          "Admin · Review Queue"
        ],
        "summary": "Approve — publish the version to the public catalog",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Decision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Published",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "Not in a reviewable state"
          }
        }
      }
    },
    "/admin/review-queue/{id}/reject": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "tags": [
          "Admin · Review Queue"
        ],
        "summary": "Reject — with notes, back to the author",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Decision"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/skills": {
      "get": {
        "tags": [
          "Admin · Skills"
        ],
        "summary": "Every skill (including private) — audit",
        "parameters": [
          {
            "name": "visibility",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Visibility"
            }
          },
          {
            "name": "owner",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Owner handle or sub"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillPage"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/skills/{slug}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        }
      ],
      "get": {
        "tags": [
          "Admin · Skills"
        ],
        "summary": "Full skill with owner and every version (audit)",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillAdmin"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Admin · Skills"
        ],
        "summary": "Upsert catalog metadata (used by the sync/generator)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SkillUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/skills/{slug}/versions/{version}/download": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Slug"
        },
        {
          "$ref": "#/components/parameters/Version"
        }
      ],
      "get": {
        "tags": [
          "Admin · Skills"
        ],
        "summary": "Download any version's package (audit)",
        "responses": {
          "302": {
            "description": "Redirect to the package"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/publications": {
      "get": {
        "tags": [
          "Admin · Publications"
        ],
        "summary": "Publications across adjacent sites (blueprint, catalog, …)",
        "parameters": [
          {
            "name": "site",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/Site"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PublicationStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Publication"
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Admin · Publications"
        ],
        "summary": "Register/create a publication",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicationCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Publication"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/publications/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "tags": [
          "Admin · Publications"
        ],
        "summary": "Get a publication",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Publication"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Admin · Publications"
        ],
        "summary": "Update a publication (status / version)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicationUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Publication"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "delete": {
        "tags": [
          "Admin · Publications"
        ],
        "summary": "Unregister a publication",
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/admin/audit-log": {
      "get": {
        "tags": [
          "Admin · Audit"
        ],
        "summary": "The audit log (every admin action and state transition)",
        "parameters": [
          {
            "name": "actor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "action",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "target_type",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuditEntry"
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "A JWT access token from the house IdP (`auth.schematize.org`), RS256, verified via JWKS\n(asymmetric only — `none`/`HS*` rejected). Discovery:\n`https://auth.schematize.org/.well-known/openid-configuration`. The app's agent obtains\none via the **OAuth 2.0 Device Authorization grant** (`POST /device_authorization`).\n"
      },
      "oidc": {
        "type": "openIdConnect",
        "openIdConnectUrl": "https://auth.schematize.org/.well-known/openid-configuration"
      }
    },
    "parameters": {
      "Slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9][a-z0-9-]*$"
        },
        "description": "Skill slug"
      },
      "Version": {
        "name": "version",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "SemVer"
      },
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "Resource id (ULID)"
      },
      "Page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 24
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid token",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Not allowed (not owner / not publisher / not admin)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict (already exists / wrong state)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooLarge": {
        "description": "Package exceeds the size limit",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Invalid package or input (bad structure",
        "path traversal": null,
        "bad SemVer)": null,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Health": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "version": {
            "type": "string"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable machine code",
            "e.g. forbidden": null,
            "not_found": null,
            "package_too_large": null
          },
          "message": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Role": {
        "type": "string",
        "enum": [
          "user",
          "publisher",
          "admin"
        ]
      },
      "UserStatus": {
        "type": "string",
        "enum": [
          "active",
          "suspended"
        ]
      },
      "Visibility": {
        "type": "string",
        "enum": [
          "private",
          "submitted",
          "published"
        ],
        "description": "A skill is private until submitted; public once a version is approved"
      },
      "VersionStatus": {
        "type": "string",
        "enum": [
          "draft",
          "submitted",
          "in_review",
          "published",
          "rejected"
        ]
      },
      "SubmissionStatus": {
        "type": "string",
        "enum": [
          "submitted",
          "in_review",
          "approved",
          "rejected",
          "withdrawn"
        ]
      },
      "PublisherRequestStatus": {
        "type": "string",
        "enum": [
          "pending",
          "approved",
          "rejected"
        ]
      },
      "PublicationStatus": {
        "type": "string",
        "enum": [
          "draft",
          "published",
          "unpublished"
        ]
      },
      "Site": {
        "type": "string",
        "enum": [
          "blueprint",
          "skills",
          "org"
        ],
        "description": "Adjacent site a publication lives on"
      },
      "Skill": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          },
          "owner": {
            "$ref": "#/components/schemas/OwnerRef"
          },
          "latest_version": {
            "type": "string",
            "nullable": true
          },
          "verified": {
            "type": "boolean"
          },
          "sponsor_name": {
            "type": "string",
            "nullable": true
          },
          "sponsor_url": {
            "type": "string",
            "nullable": true
          },
          "rating_avg": {
            "type": "number",
            "nullable": true
          },
          "rating_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SkillAdmin": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Skill"
          },
          {
            "type": "object",
            "properties": {
              "versions": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SkillVersion"
                }
              }
            }
          }
        ]
      },
      "OwnerRef": {
        "type": "object",
        "properties": {
          "sub": {
            "type": "string"
          },
          "handle": {
            "type": "string",
            "nullable": true
          },
          "display_name": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "SkillCreate": {
        "type": "object",
        "required": [
          "slug",
          "name"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]*$"
          },
          "name": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          }
        }
      },
      "SkillUpdate": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "category": {
            "type": "string"
          },
          "verified": {
            "type": "boolean"
          },
          "sponsor_name": {
            "type": "string"
          },
          "sponsor_url": {
            "type": "string"
          }
        }
      },
      "SkillVersion": {
        "type": "object",
        "properties": {
          "skill_slug": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/VersionStatus"
          },
          "changelog": {
            "type": "string",
            "nullable": true
          },
          "checksum_sha256": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "package_url": {
            "type": "string",
            "nullable": true,
            "description": "Present when you may download it"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SkillPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Skill"
            }
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "EnvironmentManifest": {
        "type": "object",
        "properties": {
          "generated_at": {
            "type": "string",
            "format": "date-time"
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "slug": {
                  "type": "string"
                },
                "version": {
                  "type": "string"
                },
                "checksum_sha256": {
                  "type": "string"
                },
                "download_url": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "PublisherRequest": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "user": {
            "$ref": "#/components/schemas/OwnerRef"
          },
          "status": {
            "$ref": "#/components/schemas/PublisherRequestStatus"
          },
          "justification": {
            "type": "string"
          },
          "reviewed_by": {
            "type": "string",
            "nullable": true
          },
          "reviewed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "reason": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublisherRequestCreate": {
        "type": "object",
        "required": [
          "justification"
        ],
        "properties": {
          "justification": {
            "type": "string",
            "description": "Who you are and what you intend to publish"
          },
          "links": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "GitHub",
            "site": null,
            "etc.": null
          }
        }
      },
      "Submission": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "skill_slug": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "submitted_by": {
            "$ref": "#/components/schemas/OwnerRef"
          },
          "status": {
            "$ref": "#/components/schemas/SubmissionStatus"
          },
          "reviewer": {
            "type": "string",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "submitted_at": {
            "type": "string",
            "format": "date-time"
          },
          "decided_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "SubmissionDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Submission"
          },
          {
            "type": "object",
            "properties": {
              "skill": {
                "$ref": "#/components/schemas/Skill"
              },
              "skill_version": {
                "$ref": "#/components/schemas/SkillVersion"
              },
              "previous_version": {
                "type": "string",
                "nullable": true,
                "description": "The currently-published version",
                "for diffing": null
              },
              "package_url": {
                "type": "string"
              }
            }
          }
        ]
      },
      "SubmissionCreate": {
        "type": "object",
        "properties": {
          "note": {
            "type": "string",
            "description": "Optional note to the reviewer"
          }
        }
      },
      "Decision": {
        "type": "object",
        "properties": {
          "notes": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "sub": {
            "type": "string"
          },
          "handle": {
            "type": "string",
            "nullable": true
          },
          "display_name": {
            "type": "string",
            "nullable": true
          },
          "roles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Role"
            }
          },
          "status": {
            "$ref": "#/components/schemas/UserStatus"
          },
          "skills_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UserPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/User"
            }
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          }
        }
      },
      "AdminUserUpdate": {
        "type": "object",
        "properties": {
          "status": {
            "$ref": "#/components/schemas/UserStatus"
          },
          "roles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Role"
            }
          }
        }
      },
      "Publication": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "site": {
            "$ref": "#/components/schemas/Site"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PublicationStatus"
          },
          "version": {
            "type": "string",
            "nullable": true
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "published_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PublicationCreate": {
        "type": "object",
        "required": [
          "site",
          "slug",
          "title"
        ],
        "properties": {
          "site": {
            "$ref": "#/components/schemas/Site"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PublicationStatus"
          }
        }
      },
      "PublicationUpdate": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PublicationStatus"
          },
          "version": {
            "type": "string"
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "actor_sub": {
            "type": "string"
          },
          "action": {
            "type": "string",
            "description": "e.g. publisher_request.approve",
            "review.approve": null,
            "user.suspend": null
          },
          "target_type": {
            "type": "string"
          },
          "target_id": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Profile": {
        "type": "object",
        "properties": {
          "handle": {
            "type": "string"
          },
          "display_name": {
            "type": "string",
            "nullable": true
          },
          "bio": {
            "type": "string",
            "nullable": true
          },
          "avatar_url": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ProfileInput": {
        "type": "object",
        "properties": {
          "handle": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          },
          "bio": {
            "type": "string"
          },
          "avatar_url": {
            "type": "string"
          }
        }
      },
      "RatingInput": {
        "type": "object",
        "required": [
          "stars"
        ],
        "properties": {
          "stars": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          }
        }
      },
      "ReviewInput": {
        "type": "object",
        "required": [
          "stars"
        ],
        "properties": {
          "stars": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          }
        }
      },
      "CommentInput": {
        "type": "object",
        "required": [
          "body"
        ],
        "properties": {
          "body": {
            "type": "string"
          },
          "parent_id": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ReportInput": {
        "type": "object",
        "required": [
          "target_type",
          "target_id",
          "reason"
        ],
        "properties": {
          "target_type": {
            "type": "string"
          },
          "target_id": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          }
        }
      }
    }
  }
}