aired

Docs

Install aired four ways. All paths are no-auth and free. Source on GitHub.

Skill

Adds a SKILL.md to your project so any AI agent (Claude Code, Cursor, Windsurf, Codex) discovers and uses aired automatically. This is the recommended path.

npx skills add progrmoiz/aired -g
  • -g installs the skill user-globally (under ~/<agent>/skills/) so it works across all projects.
  • The skills CLI auto-detects which agent is running it and targets that agent.
  • Powered by skills.sh.

CLI

Publishes a file or directory and prints the URL. Inlines CSS, JS, and images automatically. Pipe-friendly.

npx aired dashboard.html

Common flags

FlagEffect
--ttl <dur>Expiry: 1h, 24h, 7d, 30d
--permanentNo expiry
--pin <pin>PIN-protect viewing
-t, --title <title>Custom page title
--reads <n>Auto-delete after N views
--jsonPrint { url, update_token, expiresAt }

Other commands

CommandDescription
aired ./dir/Bundle a directory into one HTML
aired update <id> <file>Update an existing page
aired delete <id>Delete a page
aired info <id>Show page metadata
aired tokensList stored update tokens
aired tokens pruneDrop tokens for expired pages
aired doctorDiagnostics (Node.js, API reachability, token store)

The CLI persists update tokens locally via conf. On macOS that lands in ~/Library/Preferences/aired-nodejs/; on Linux ~/.config/aired-nodejs/.

MCP server

Streamable HTTP at https://aired.sh/mcp. Exposes a publish_html tool — your AI publishes artifacts directly through the MCP transport.

Claude Code

claude mcp add aired --transport http https://aired.sh/mcp

Cursor · .cursor/mcp.json

{
  "mcpServers": {
    "aired": { "url": "https://aired.sh/mcp" }
  }
}

VS Code · .vscode/mcp.json

{
  "servers": {
    "aired": { "type": "http", "url": "https://aired.sh/mcp" }
  }
}

Windsurf · ~/.codeium/windsurf/mcp_config.json

{
  "mcpServers": {
    "aired": { "serverUrl": "https://aired.sh/mcp" }
  }
}

Codex · ~/.codex/config.toml

[mcp_servers.aired]
url = "https://aired.sh/mcp"

STDIO bridge (any client)

{
  "mcpServers": {
    "aired": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://aired.sh/mcp"]
    }
  }
}

Tool: publish_html

Single tool. Inputs:

{
  "html":      "string, required, <= 2 MB",
  "title":     "string, optional",
  "ttl":       "number (seconds), optional",
  "permanent": "boolean, optional",
  "pin":       "string, optional",
  "reads":     "number, optional, auto-delete after N views"
}

Returns { id, url, update_token, expiresAt }.

HTTP API

No SDK, no auth. POST your HTML and get a URL back.

curl -X POST https://aired.sh/api/publish \
  -H "Content-Type: application/json" \
  -d '{"html": "<h1>Hello</h1>"}'

Response:

{
  "url": "https://aired.sh/p/a1b2c3d4e5",
  "update_token": "tok_...",
  "expiresAt": "2026-05-16T12:00:00.000Z"
}

Endpoints

MethodPathDescription
POST/api/publishCreate a page. Returns { id, url, update_token, expiresAt }. Also updates if id + update_token are in the body.
PUT/api/pages/:idUpdate by update_token in the JSON body.
DELETE/api/pages/:idDelete by Authorization: Bearer <token> header.
GET/api/pages/:idPublic metadata: { id, title, size, readCount, expiresAt }. No auth.
GET/api/statsAggregate counts and recent publishes for the homepage feed.
POST/api/reportBody { id, reason }. Flag a page for abuse review.
GET/og/:idGenerated Open Graph image for the page.
GET/p/:idPublic viewer for a published page.
POST/p/:id/verify-pinSubmit a PIN to view a protected page.

Request body — POST /api/publish

{
  "html": "<!doctype html>...",   // required, max 2 MB
  "title": "My dashboard",        // optional, auto-extracted from <title> if omitted
  "ttl": 604800,                  // optional seconds (default 604800 = 7 days)
  "permanent": false,             // optional, true = no expiry (overrides ttl)
  "pin": "1234",                  // optional, viewer must enter to view
  "reads": 10,                    // optional, auto-delete after N views

  // To update an existing page in one call, also include:
  "id": "a1b2c3d4e5",
  "update_token": "tok_..."
}

Request body — PUT /api/pages/:id

{
  "html": "<!doctype html>...",   // required
  "title": "Optional new title",  // optional
  "update_token": "tok_..."       // required, in body (not header)
}

Update via curl

curl -X PUT https://aired.sh/api/pages/a1b2c3d4e5 \
  -H "Content-Type: application/json" \
  -d '{"html":"<h1>v2</h1>","update_token":"tok_..."}'

Delete via curl

curl -X DELETE https://aired.sh/api/pages/a1b2c3d4e5 \
  -H "Authorization: Bearer tok_..."

Auth

  • Publishing is anonymous — no API key required.
  • Every publish returns an update_token. It is the only credential for that page.
  • PUT takes the token in the JSON body as update_token.
  • DELETE takes the token as Authorization: Bearer <token> header.
  • POST can act as update when id and update_token are both in the body.
  • Lose the token and you can't mutate the page (you can still publish a fresh one).

Errors

All errors are JSON: { "error": "<message>" }.

StatusWhen
400Invalid JSON body, missing html, validation failed.
401DELETE missing Authorization: Bearer header.
403update_token does not match the page.
404Page id does not exist (or expired).
410Page exists but has expired.
429Rate limit (5 publishes/hour/IP). Wait an hour.
503Storage write limit reached. Retry shortly.

PIN protection

Pass pin when publishing. The viewer at /p/:id shows a PIN form before serving the HTML.

Submit a PIN

curl -X POST https://aired.sh/p/a1b2c3d4e5/verify-pin \
  -H "Content-Type: application/json" \
  -d '{"pin":"1234"}'

On success, the server sets an HttpOnly, SameSite=Strict cookie scoped to /p/:id with a 1-hour TTL — subsequent loads are pass-through until it expires.

Limits

Max artifact size2 MB
Publishes per hour per IP5
Default TTL7 days
Max TTLpermanent

Need more? aired is open source — run your own.

Self-hosting

aired runs as a single Cloudflare Worker with a KV namespace for metadata and an R2 bucket for HTML.

  • Clone progrmoiz/aired.
  • Create your own KV namespace and R2 bucket; update apps/worker/wrangler.toml with the IDs.
  • pnpm install && pnpm run build && cd apps/worker && pnpm wrangler deploy.
  • Point a custom domain or use the default workers.dev subdomain.

Agent surfaces

For agents that want context before acting: