For agents

host0 — publish a folder, get a live URL.

Base URL: https://host0.ai

Supported today: static sites — HTML, CSS, JS, images/fonts/assets; ≤1000 files, ≤25 MB/file, ≤100 MB total; index.html required (or deploy a single file on its own — it serves at /). Shared data: declare record collections in h0.json and read/write them from the page at /.h0/data/… (docs: /docs#shared-data). HTML pages on the free plan get a small "Hosted on host0" badge fixed bottom-right — leave that corner clear. Server code is on the roadmap.

Human here? The visual landing page has the pitch, the demo, and the waitlist. This page is the plain-text version, written for agents.

Install the skill

Two files: an instruction file and one Bash script. Requires curl, jq, and shasum/sha256sum. The paths below are where Claude Code keeps skills — other agents keep them elsewhere, so use whichever directory yours reads.

mkdir -p ~/.claude/skills/host0/scripts
curl -sS https://host0.ai/skill.md -o ~/.claude/skills/host0/SKILL.md
curl -sS https://host0.ai/publish.sh -o ~/.claude/skills/host0/scripts/publish.sh
chmod +x ~/.claude/skills/host0/scripts/publish.sh

Paste this into your agent

It installs the skill, signs in with a link you approve in your browser, and asks you what to publish. Nothing to type into the chat.

Set me up with host0 — the cloud for small software, where a folder becomes a live URL.

1. Install the host0 skill: save https://host0.ai/skill.md as SKILL.md, and https://host0.ai/publish.sh as scripts/publish.sh beside it, then chmod +x scripts/publish.sh. In Claude Code that folder is ~/.claude/skills/host0/ — other agents keep skills elsewhere, so use whichever directory mine reads.
2. Read SKILL.md, then curl https://host0.ai/api/v1 for the live contract. If the two disagree, trust the API.
3. Sign me in with a device login — don't ask for my email or password: run scripts/publish.sh --login, show me the link and code it prints, and once I've approved in my browser run scripts/publish.sh --login-wait — it saves the key to ~/.host0/credentials with chmod 600.

Then ask me what I want to publish.

Get an API key

Device login: the agent sends nothing personal. It prints a link and a short code, the human approves it on host0 in their own browser, and the script saves the key. With no key, a publish starts this on its own — exit code 3 means “show the link and code, then run again”. The key starts h0_, is returned exactly once, and travels as Authorization: Bearer h0_… on every authenticated request.

# 1. start a sign-in — no email, nothing personal leaves this machine
#    prints a link + code and exits 3: show both to the user
~/.claude/skills/host0/scripts/publish.sh --login

# 2. wait for the approval — saves the key to ~/.host0/credentials (chmod 600)
~/.claude/skills/host0/scripts/publish.sh --login-wait

The same over plain REST:

# 1. start — returns device_code, user_code and verification_uri_complete
curl -sS -X POST https://host0.ai/api/v1/auth/device

# 2. show the user verification_uri_complete + user_code, then poll every
#    "interval" seconds until they approve (authorization_pending until then)
curl -sS --retry 3 --retry-all-errors https://host0.ai/api/v1/auth/device/token \
  -H "content-type: application/json" \
  -d '{"device_code": "…"}'

# 3. save the returned api_key yourself — don't make the human run this
mkdir -p ~/.host0 && printf '%s' "h0_your_key_here" > ~/.host0/credentials && chmod 600 ~/.host0/credentials

Email code (fallback) — only for an agent on the human's own machine, never from a cloud sandbox:

# fallback — only for an agent on your own machine, never from a cloud sandbox
# 1. request a 6-digit code — host0 emails it
curl -sS https://host0.ai/api/v1/auth/request-code \
  -H "content-type: application/json" \
  -d '{"email": "you@example.com"}'

# 2. exchange the code for an api key (returned exactly once)
curl -sS https://host0.ai/api/v1/auth/verify-code \
  -H "content-type: application/json" \
  -d '{"email": "you@example.com", "code": "123456"}'

# 3. save it yourself — don't make the human run this
mkdir -p ~/.host0 && printf '%s' "h0_your_key_here" > ~/.host0/credentials && chmod 600 ~/.host0/credentials

Publish

Publish a built static site with index.html at the root of the directory — its contents become the site root. The script handles hashing, the manifest, uploads, and finalize.

# first publish — creates the app
~/.claude/skills/host0/scripts/publish.sh ./my-site --name "my site"

# every publish after that — same folder, same app, unchanged files skipped
~/.claude/skills/host0/scripts/publish.sh ./my-site

Or drive the REST API directly — four steps:

  1. 01POST https://host0.ai/api/v1/apps — body {"name": "my site"} → app_id, slug (random; rename later with PATCH {"slug"}), url
  2. 02POST https://host0.ai/api/v1/apps/{app}/deployments — body {"files": [{path, size, sha256}]} → upload.targets (unchanged files come back in upload.skipped)
  3. 03PUT each target url — raw bytes, size and sha256 must match the manifest
  4. 04POST https://host0.ai/api/v1/deployments/{id}/finalize — flips the deployment live, atomically

Nothing is live until finalize succeeds — a half-finished upload never serves, and the previous deployment keeps serving until the flip.

On the free plan, host0 adds a small “Hosted on host0” badge fixed to the bottom-right corner of every HTML page (PDFs, images and other files are untouched) — leave a little room there and don't try to hide it; paid plans will be able to remove it.

Shared data

An app is not limited to static content. Put an h0.json at the root of the deploy declaring record collections, and the page reads and writes them at /.h0/data/{collection} on its own origin — a team todo, a ticket board, an RSVP list, with no server code and no connection string.

{
  "records": {
    "tickets": {
      "fields": {
        "title":    { "type": "string", "required": true, "maxLength": 200 },
        "done":     { "type": "boolean", "default": false },
        "status":   { "type": "string", "enum": ["todo", "doing", "done"], "default": "todo" },
        "assignee": { "type": "string" },
        "notes":    { "type": "string", "maxLength": 5000 }
      },
      "access": {
        "read":   "viewers",
        "create": "viewers",
        "update": "members",
        "delete": "members"
      }
    }
  }
}

Audiences are viewers (anyone who can open the app, including anonymous visitors of a public one), members (the owner plus invited emails), owner (the app's owner only), creator (each signed-in person, their own records only), or an explicit list of emails. By default everyone the app admits can read and create, while update and delete need an invite (`members`). The owner always passes every rule.

Decide whose data it is before writing `h0.json` — and if the user hasn't said, ask them. Shared: everyone the app admits works on the same records (a team board, a poll, a guestbook) — leave `read` on `viewers` or `members`. Per-person: each visitor signs in and gets their own private records (a habit tracker, a journal, a reading list anyone can use from the same URL) — set `read`, `create`, `update` and `delete` all to `creator`, and when `/.h0/me` says `{anonymous: true}` show a sign-in screen with its `signin_url` instead of loading data. `owner` is neither: it means only you, the app's owner — every other visitor is shut out.

On a public app, call `/.h0/me` when the page loads and, if it answers `{anonymous: true}`, render its `signin_url` as a visible sign-in link — a link the visitor chooses to follow, never an automatic redirect on page load. Members are not recognised automatically — reading is open, so nothing sends them through the gate on their own. Handle 401 on writes too: the body carries `signin_url`.

The full shape, with a worked team-todo example, is in the docs.

Visibility

Every app goes live public at an unlisted URL unless you ask for something else. An app uses one visibility at a time:

Set it on publish with --visibility private|link|public, or any time with PATCH /api/v1/apps/{app}.

Limits

Upload (REST + skill)   1000 files · 25 MB (26214400)/file · 100 MB (104857600) total
Account                 100 apps · 200 deploys/hour · 500 MB (524288000) stored
API rate                120 requests/minute per API key

Parenthesised figures are exact byte counts. These are rendered from the same constants the server enforces — GET /api/v1 returns them as JSON under limits. Over a cap you get bad_request (a manifest over the upload caps), app_limit_reached, deploy_rate_limited, storage_quota_exceeded or rate_limited, each with a message saying what to change.

MCP

Deploying is the skill's job. To manage apps you've deployed from Claude and other MCP clients, add host0 as a remote server:

claude mcp add --transport http host0 https://host0.ai/mcp

Sign in when prompted; the tools list and inspect apps, change visibility, rename an app's URL, share and unshare by email, and delete an app (live or not). They don't deploy — to put new files live, always use the skill. Custom domains will land here later.

Machine surfaces