---
title: "Save the private draft"
method: PUT
path: "/drops/{slug}/draft"
---

# Save the private draft

`PUT /drops/{slug}/draft`

## Request

- Base URL: `https://ship.page`
- URL: `https://ship.page/drops/{slug}/draft`
- Auth: HTTP bearer

Replaces the whole draft file list. Compare-and-swap: send the draft_rev you last saw, or 0 to start a draft. Drafts are never served; publish to go live.

## Request body

- object
  - `draft_rev` integer, required
  - `files` object[], required
    - `path` string, required
    - `sha256` string, required

## Response `200`

Draft saved.

- EditorDraft
  - `draft_rev` integer, required: Send it back on the next draft write; it changes on every write.
  - `updated_at` string, date-time, required
  - `files` EditorFile[], required
    - `path` string, required
    - `bytes` integer, required
    - `sha256` string, required
    - `mime` string, required

## Other responses

- `400`: bad_request (bad path) or unknown_blob: upload the hashes listed in `missing`, then retry.
- `401`: Missing or invalid credential.
- `402`: Subscriber-gated feature on a free account. The body's `hint` and `upgrade_url` tell the user how to upgrade.
- `404`: Not found (also returned for resources owned by someone else).
- `409`: draft_changed: the draft changed elsewhere. The body's `draft` is the server copy (null when it was published or discarded).
- `410`: The drop has expired (expired).
- `413`: too_large: over 200 files or 20 MB.
- `429`: Rate limited. Back off per the Retry-After header (60s).
- `503`: Required backend unavailable. Password creation and rotation fail closed when billing cannot be verified; retry later.

## Changes

- **2026-10-20** `99aad34506e8`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/ship/apis/ship-page/changes/drops/:slug/draft/put.md)

---

[API](https://skmtc.dev/ship/apis/ship-page.md) · [All operations](https://skmtc.dev/ship/apis/ship-page/llms.txt) · [OpenAPI document](https://skmtc.dev/ship/apis/ship-page/revisions/99aad34506e8?raw)
