# draftlet.io — agent quickstart

Publish HTML to a live URL with three HTTP calls. No account required for anonymous sites; they expire after 24 hours.

## Flow

1. **POST `/api/v1/publish`** with a manifest of every file you'll upload.
   The API returns a slug, an `editToken`, and a presigned S3 PUT URL for each file.
2. **PUT each file** directly to its presigned URL.
3. **POST `/api/v1/publish/<slug>/finalize`** with the `versionId` and `editToken`.
   The site is now live at `https://<slug>.draftlet.io`.

## Example (single index.html)

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

# 1. Announce
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')

# 2. Upload (Content-Type and Content-Length must match the manifest)
curl -X PUT -H "content-type: text/html" --data-binary @index.html "$URL"

# 3. Finalize
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 at: https://$SLUG.draftlet.io"
```

## Multi-file example

```jsonc
// POST body
{
  "title": "My agent's demo",
  "files": [
    { "path": "index.html",   "contentType": "text/html",       "size": 1240 },
    { "path": "style.css",    "contentType": "text/css",        "size": 480 },
    { "path": "app.js",       "contentType": "application/javascript", "size": 2100 }
  ]
}
```

You'll get one presigned URL per file in `uploads[]`. PUT all of them, then finalize. Order doesn't matter; finalize fails with `no_files_uploaded` if any PUT was skipped.

## Constraints

| Limit                  | Value              |
|------------------------|--------------------|
| Max files per site     | 100                |
| Max size per site      | 25 MB              |
| Max size per file      | 5 MB               |
| Anonymous TTL          | 24 h after go-live |
| Presigned URL TTL      | 15 minutes         |
| Finalize window        | 60 min after publish (pending sites are reaped after that) |
| Publish rate limit     | 20 / IP / hour     |
| Finalize rate limit    | 40 / IP / hour     |

## Iterating (republish)

To update an existing site, **POST `/api/v1/publish/<slug>`** with `{editToken, files}` — same manifest shape as the first publish. You get a new `versionId` and fresh presigned URLs; PUT the files and finalize as usual. The slug and URL stay the same.

```bash
curl -s -X POST "https://api.draftlet.io/api/v1/publish/$SLUG" \
  -H "content-type: application/json" \
  -d "{\"editToken\":\"$TOK\",\"files\":[{\"path\":\"index.html\",\"contentType\":\"text/html\",\"size\":$SIZE}]}"
# → PUT files to uploads[], then finalize with the new versionId
```

## Rollback

Every publish keeps its files. List versions with **GET `/api/v1/sites/<siteId>/versions`**, then finalize with an **older** `versionId` to re-promote it — that's the whole rollback.

## Errors you might hit

| Code               | Meaning                                                       |
|--------------------|---------------------------------------------------------------|
| `missing_index`    | Manifest must include `index.html`                            |
| `slug_taken`       | If you passed an explicit `slug`, pick another                |
| `invalid_slug`     | Slug must match `[a-z0-9-]`, no leading/trailing dash         |
| `too_many_files`   | Reduce file count or bundle assets                            |
| `site_too_large`   | Total uploaded bytes exceed the per-site cap                  |
| `file_too_large`   | A single file exceeds the 5 MB per-file cap                   |
| `no_files_uploaded`| Finalize called before PUTs succeeded — retry the PUTs        |
| `invalid_token`    | `editToken` doesn't match the site                            |
| `rate_limited`     | HTTP 429 — honor the `retry-after` header (limits above)      |

## Bring your own domain

```bash
# 1. Register (editToken or owner JWT required)
curl -s -X POST "https://api.draftlet.io/api/v1/sites/$SITE_ID/domains" \
  -H "content-type: application/json" \
  -d "{\"domain\":\"www.example.com\",\"editToken\":\"$TOK\"}"
# → returns step1_validation_cname + step2_routing_cname to add at your DNS provider

# 2. Add both CNAMEs, then poll until active (cert issuance: minutes to 72h)
curl -s -X POST "https://api.draftlet.io/api/v1/sites/$SITE_ID/domains/www.example.com/verify" \
  -H "content-type: application/json" -d "{\"editToken\":\"$TOK\"}"
```

Once `active`, `https://www.example.com` serves your site with its own certificate. `DELETE .../domains/<domain>` detaches it.

## Want a custom slug?

Pass `"slug": "your-name"` in step 1. Slug must be lowercase alphanumeric + dashes, 1–63 chars. Auto-generated slugs look like `quiet-river-4821`.
