> ## Documentation Index
> Fetch the complete documentation index at: https://docs.masterskills.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Publishing

> The two-phase publish pipeline: prepare a draft, upload, complete under a server-side scan.

Publishing is two-phase so a human approval can sit between packaging and upload — the CLI enforces that pause; API consumers must honor it too.

## `POST /publish/prepare`

Registers a draft from a client-built manifest and returns a presigned upload URL. **No file content is sent here** — only paths, sizes, and hashes.

```json theme={"dark"}
// request
{
  "org": "acme",
  "slug": "review-checklist",
  "displayName": "Review checklist",
  "description": "…",
  "visibility": "private",
  "manifest": {
    "files": [
      { "path": "SKILL.md", "size": 4096, "sha256": "…" },
      { "path": "references/standards.md", "size": 20480, "sha256": "…" }
    ],
    "totalSize": 24576
  }
}

// 200
{
  "draftId": "…",
  "name": "@acme/review-checklist",
  "org": "acme",
  "uploadUrl": "https://…",        // presigned PUT for the package
  "nextVersion": 4,
  "warnings": []
}
```

`org` defaults to the device's home org; membership in the target org is required.

Validation on prepare:

| Check                                           | Failure           |
| ----------------------------------------------- | ----------------- |
| Slug format (lowercase letters, digits, dashes) | `400`             |
| `SKILL.md` present in the manifest              | `422`             |
| Caps: 100 MB package, 50 MB per file            | `422`             |
| Path sanity (no traversal, no absolute paths)   | `422`             |
| Secret-looking files in the manifest            | `422 secret_file` |

## `POST /publish/:draftId/complete`

After uploading the package to `uploadUrl`, complete the publish. The draft is bound to its creator.

```json theme={"dark"}
// 200
{
  "skill": { "name": "@acme/review-checklist", "org": "acme", "slug": "review-checklist" },
  "version": { "number": 4, "contentHash": "…" }
}
```

| Status | Body                        | Meaning                                                                                    |
| ------ | --------------------------- | ------------------------------------------------------------------------------------------ |
| `422`  | `{ "scanFindings": [ … ] }` | The server-side secret scan found something — **nothing was published**; show the findings |
| `422`  | `manifest_mismatch`         | Uploaded bytes differ from the prepared manifest — what was approved is not what arrived   |

The server never auto-publishes and never silently drops a finding — rejections carry the details, for a human to see.
