---
name: handoff
description: Upload files to Handoff for instant hosting with versioned URLs
version: 5
update_url: https://handoff.host/.well-known/agent-skills/handoff/SKILL.md
---

# Handoff Upload Skill

Upload HTML, Markdown, or image files to Handoff for instant, versioned hosting.

## Prerequisites

The following environment variable must be set:

- `HANDOFF_API_TOKEN` - Your API token (find it at **Dashboard > Settings**)
- `HANDOFF_HOST` - (Optional) API host. Defaults to `https://handoff.host`

If `HANDOFF_API_TOKEN` is not set, stop and tell the user:
> Set your Handoff API token with: `export HANDOFF_API_TOKEN=your_token_here`
> You can find your token at Dashboard > Settings.

## Supported File Types

| Type     | Extensions                              | Max Size |
|----------|-----------------------------------------|----------|
| HTML     | `.html`, `.htm`                         | 2 MB (Pro: 10 MB) |
| Markdown | `.md`                                   | 2 MB (Pro: 10 MB) |
| Image    | `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp`, `.svg` | 5 MB (Pro: 20 MB) |

The Free plan allows 5 files. Uploading a new version to an existing slug always works, even at the limit.

## Upload Command

```bash
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/upload/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -F "file=@<filepath>" \
  -F "slug=<slug>" \
  -F "note=<optional note>" \
  -F "password=<optional password>"
```

### Parameters

- `file` (required) - The file to upload. Must be one of the supported types above.
- `slug` (required) - URL slug for the file. Must be 3-50 characters, lowercase letters, numbers, and hyphens only.
- `note` (optional) - A short description or version note.
- `password` (optional) - Set a password to protect the file. Visitors will need to enter this password to view the file.

### Image Assets

When uploading HTML or Markdown files that reference local images, include the images as assets:

```bash
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/upload/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -F "file=@<filepath>" \
  -F "slug=<slug>" \
  -F "assets=@<image1>" \
  -F "assets=@<image2>"
```

The server automatically rewrites image references in the HTML/Markdown to point to the hosted assets.

### Password Protection

To set a password when uploading:

```bash
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/upload/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -F "file=@<filepath>" \
  -F "slug=<slug>" \
  -F "password=mypassword"
```

To manage passwords on existing files:

```bash
# Set or update password
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/files/<slug>/password/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"password": "newpassword"}'

# Remove password
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/files/<slug>/password/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"password": null}'
```

## Response

Success returns JSON:

```json
{
  "url": "/@username/slug/",
  "slug": "my-page",
  "version": 1,
  "file_type": "html"
}
```

The full shareable URL is `${HANDOFF_HOST:-https://handoff.host}` followed by the `url` field from the response.

## Error Handling

| Status | Meaning | Action |
|--------|---------|--------|
| 401    | Invalid or missing API token | Tell user to check `HANDOFF_API_TOKEN` |
| 400 `"Invalid slug"` | Slug format invalid | Slug must be 3-50 chars: lowercase letters, numbers, hyphens |
| 400 `"File too large"` | File exceeds size limit | HTML/Markdown max 2 MB, images max 5 MB (Pro: 10 MB / 20 MB) |
| 400 `"Unsupported file type"` | Wrong file extension | Only the extensions listed in Supported File Types are allowed |
| 402 `"code": "plan_limit"` | Free plan limit reached (files, file size, or password) | Show the user the `error` text and `upgrade_url` exactly as given. Don't retry, and don't work around it (for example by overwriting another slug) |

## Checking for Updates

This skill includes a `version` field in its frontmatter. To check if a newer version is available, compare your local version against the hosted one:

```bash
curl -s "https://handoff.host/.well-known/agent-skills/handoff/SKILL.md" | head -5 | grep "version:"
```

If the hosted version number is higher than your local `version:` field, download the update from the `update_url` in the frontmatter.

## Workflow

Follow these steps when the user asks to upload or host a file:

1. **Check token** - Verify `HANDOFF_API_TOKEN` is set. If not, tell the user how to set it.
2. **Prepare the file** - If the content exists only in the conversation (not on disk), write it to a temporary file with the correct extension (e.g., `/tmp/handoff-upload.html`).
3. **Scan for image references** - If the file is HTML or Markdown, scan for local image references (`<img src="...">`, `![alt](...)`, `url(...)`). Check if the referenced image files exist on disk (relative to the file's directory). If images are found, include them as `-F "assets=@path/to/image"` in the upload command. If referenced images can't be found on disk, warn the user.
4. **Determine the slug** - Derive from the filename or ask the user. Normalize to lowercase with hyphens, 3-50 characters.
4.5. **Ask about password** - If the user wants to restrict access, include `-F "password=<password>"` in the upload command.
5. **Upload** - Run the curl command above.
6. **Parse the response** - Extract the JSON fields. On success, build the full URL and present it to the user.
7. **Clean up** - Remove any temporary files created in step 2.

## Examples

Upload an HTML file:

```bash
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/upload/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -F "file=@/tmp/landing-page.html" \
  -F "slug=landing-page" \
  -F "note=Initial version"
```

Upload a Markdown file:

```bash
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/upload/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -F "file=@./notes.md" \
  -F "slug=meeting-notes"
```

Re-upload to create a new version (same slug automatically increments the version):

```bash
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/upload/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -F "file=@./updated-page.html" \
  -F "slug=landing-page" \
  -F "note=Updated hero section"
```

Upload an HTML file with images:

```bash
curl -s -X POST "${HANDOFF_HOST:-https://handoff.host}/api/upload/" \
  -H "Authorization: Bearer $HANDOFF_API_TOKEN" \
  -F "file=@/tmp/landing-page.html" \
  -F "slug=landing-page" \
  -F "assets=@/tmp/hero.png" \
  -F "assets=@/tmp/logo.svg" \
  -F "note=Initial version with images"
```
