---
name: draftlet
description: Publish HTML to draftlet.io and get a live URL at https://<random-slug>.draftlet.io. Anonymous sites expire in 24 hours. Use when the user asks to "publish this page", "deploy this HTML", "give me a shareable link for this", "host this on draftlet", or wants a quick public URL for an artifact you just generated. The API is open and anonymous, no auth needed.
---

# draftlet.io publish skill

Three HTTP calls. No account required.

## Quick reference

API base: `https://api.draftlet.io`
Live URL pattern: `https://<slug>.draftlet.io`

| Limit              | Value                |
|--------------------|----------------------|
| Files per site     | 100                  |
| Total size         | 25 MB                |
| Per-file size      | 5 MB                 |
| Anonymous TTL      | 24 hours after go-live |
| Presigned URL TTL  | 15 min               |
| Finalize window    | 60 min after publish |
| Rate limits        | 20 publishes, 40 finalizes / IP / hour |

## How to publish

1. **POST `/api/v1/publish`** with a manifest of every file. Body:
   ```json
   {
     "files": [
       { "path": "index.html", "contentType": "text/html", "size": 1234 }
     ]
   }
   ```
   Response gives `slug`, `versionId`, `editToken`, and a presigned `uploadUrl` per file. **`index.html` is required.**

2. **PUT each file** to its presigned `uploadUrl`:
   ```bash
   curl -X PUT -H "content-type: text/html" --data-binary @index.html "$UPLOAD_URL"
   ```
   The `Content-Type` and `Content-Length` MUST match the manifest values. Use `--data-binary @file` (not `--data`) so binary content isn't mangled.

3. **POST `/api/v1/publish/<slug>/finalize`** with `{versionId, editToken}`. The site is now live at `https://<slug>.draftlet.io`.

## End-to-end example

```bash
SIZE=$(wc -c < index.html | tr -d ' ')

RESP=$(curl -s -X POST https://api.draftlet.io/api/v1/publish \
  -H "content-type: application/json" \
  -d "{\"files\":[{\"path\":\"index.html\",\"contentType\":\"text/html\",\"size\":$SIZE}]}")

SLUG=$(echo "$RESP" | jq -r .slug)
VER=$(echo  "$RESP" | jq -r .versionId)
TOK=$(echo  "$RESP" | jq -r .editToken)
URL=$(echo  "$RESP" | jq -r '.uploads[0].uploadUrl')

curl -X PUT -H "content-type: text/html" --data-binary @index.html "$URL"

curl -s -X POST "https://api.draftlet.io/api/v1/publish/$SLUG/finalize" \
  -H "content-type: application/json" \
  -d "{\"versionId\":\"$VER\",\"editToken\":\"$TOK\"}"

echo "Live: https://$SLUG.draftlet.io"
```

## When to use a custom slug

By default the API generates a random slug like `quiet-river-4821`. If the user has a preferred name, pass `"slug": "their-name"` in step 1 (lowercase alphanumeric + dashes, 1–63 chars, must be globally unique). Otherwise let it auto-generate.

## After publish

- Tell the user the live URL (`https://<slug>.draftlet.io`) and the 24-hour expiry.
- Save the `editToken` — it's required for finalize, republish, rollback, and claiming.

## Republishing (update the same URL)

`POST /api/v1/publish/<slug>` with `{editToken, files}` — same manifest shape as the first publish. Returns a new `versionId` + fresh presigned URLs. PUT the files, then finalize with the new `versionId`. The URL doesn't change, and each finalize resets the 24h expiry.

To roll back: `GET /api/v1/sites/<siteId>/versions`, then finalize with an older `versionId`.
- For multi-file sites (CSS, JS, images), include all files in the manifest and PUT each one before finalizing. The finalize step fails with `no_files_uploaded` if any PUT was skipped.

## Errors

| Code               | Recovery                                                |
|--------------------|---------------------------------------------------------|
| `missing_index`    | Add `index.html` to your manifest                       |
| `slug_taken`       | Pick a different slug or omit it for auto-generation    |
| `invalid_slug`     | Slug must match `[a-z0-9-]`, 1–63 chars                 |
| `too_many_files`   | Bundle / inline assets to stay under 100 files          |
| `site_too_large`   | Reduce total bytes under 25 MB                          |
| `file_too_large`   | Reduce that file under 5 MB                             |
| `no_files_uploaded`| Finalize was called before all PUTs landed — retry PUTs |
| `invalid_token`    | Wrong `editToken` for this slug                         |
| `rate_limited`     | HTTP 429 — wait for the `retry-after` seconds, then retry |

Full spec: <https://draftlet.io/quickstart.md>
