The one command
Run it from the folder that holds your site (or point it at a single file):
curl -fsSL https://chorus.host/publish.py | python3 - ./site
It prints JSON like this (claim token shortened here):
{
"url": "https://navy-twig-20.chorus.host",
"slug": "navy-twig-20",
"anonymous": true,
"expiresAt": "2026-10-02T05:30:23Z",
"files": 2,
"claimUrl": "https://chorus.host/claim/navy-twig-20#ctk_REDACTED",
"claimToken": "ctk_REDACTED"
}
Live at the url for 24 hours. To keep it, open the claimUrl, enter your email and the 6-digit code we send you.
Measured on 1 October 2026 (one run, two files, from a Mac in the San Francisco Bay Area): 3.0 seconds from pressing Enter to a published site, including downloading the script, and 3.4 seconds until the link answered 200.
Only have curl? One HTML file is one request:
curl -sS https://chorus.host/v1/publish -F file=@index.html
Or drop a file, a folder or a .zip here:
Links last 24 hours. Sign in (free) to keep them.
Pick a way to publish
| You have | Use |
|---|---|
| A folder or a file, and Python 3 | curl -fsSL https://chorus.host/publish.py | python3 - ./site |
| One file and curl, or a zipped folder | curl -sS https://chorus.host/v1/publish -F file=@index.html (or -F archive=@site.zip) |
| A script or CI job publishing big folders | The HTTP API in three calls |
| An agent with a shell (Claude Code, Codex, Cursor) | npx skills add chorus-host/skill -g, or the one command above |
| An agent with no shell (claude.ai, other MCP clients) | The MCP server https://chorus.host/mcp, tool publish_site (setup) |
| A build folder you deploy again and again | beacon deploy ./dist (the CLI) |
| No terminal at all | The drop zone above, or chorus.host/html-to-url |
Host a static site without an account in one command
publish.py is a standard-library Python script; nothing to install. What it does:
- Publishes a folder, or a single file. A lone
.htmlfile becomesindex.html, so the link opens it. - Takes an optional name:
python3 - ./site my-sitepublishes tohttps://my-site.chorus.host. - Remembers what it published in
.beacon/publish.json, so running it again from the same folder updates the same site and keeps the URL. - Never uploads
.gitand other version-control folders,node_modules, virtualenvs, anything your.gitignoreor.beaconignorematches, or files that usually hold secrets:.envand.env.*,.npmrc,.pypirc,.netrc, SSH keys,*.pem,*.key,*.p12,*.pfx. Each skipped path is listed. - Prints one JSON object:
url,slug,expiresAt,claimUrl,claimToken, plusfileUrlwhen you published a single file. - Reads
CHORUS_CLIENT=<agent>/<version>if you want to tell us which agent sent the publish.
If you have an API key in CHORUS_API_KEY, BEACON_API_KEY or ~/.config/beacon/config.json, the site goes into your account and doesn't expire. Pass --anonymous to ignore the key.
Publish from an AI agent and let a human claim it later
Agents that can run commands only need the one command above, or the skill: npx skills add chorus-host/skill -g teaches Claude Code, Codex, Cursor and Gemini CLI how to publish and what to tell you afterwards.
Agents without a shell use the MCP server. In Claude Code:
claude mcp add --transport http chorus https://chorus.host/mcp
In any client that takes a JSON config (mcp.json):
{ "mcpServers": { "chorus": { "type": "http", "url": "https://chorus.host/mcp" } } }
On claude.ai, add https://chorus.host/mcp as a custom connector. The publish_site tool takes up to 4 MB of files per call without an API key (10 MB with one) and up to 1,000 files. All clients, including ChatGPT's limits.
Paste this to your agent:
Publish this folder to chorus.host and give me the link and the claim link, exactly as returned. Instructions: https://chorus.host/skill.md
The hand-off rule the skill gives agents: show the live URL on its own line, and the claim link character for character. The code after # in the claim link is 47 characters; if an agent shortens or rewraps it, the link breaks.
The HTTP API in three calls
One request (/v1/publish) is the simple path for small sites: up to 4 MB of files without an account. The three-call API handles big sites: register the files with their sizes and SHA-256 hashes, upload each one to the URL you get back, then make the version live. The hash is what lets chorus.host skip files it already has, so re-publishing a big folder only uploads what changed. One file:
FILE=index.html
HASH="sha256:$( (shasum -a 256 "$FILE" 2>/dev/null || sha256sum "$FILE") | cut -d' ' -f1)"
SIZE=$(wc -c < "$FILE" | tr -d ' ')
curl -sS https://chorus.host/v1/sites -H "Content-Type: application/json" \
-d '{"files":[{"path":"index.html","size":'"$SIZE"',"contentType":"text/html; charset=utf-8","hash":"'"$HASH"'"}]}' \
> site.json
UPLOAD_URL=$(jq -r '.uploads.pending[0].uploadUrl // empty' site.json)
[ -z "$UPLOAD_URL" ] || curl -sS -X PUT "$UPLOAD_URL" \
-H "Content-Type: text/html; charset=utf-8" --data-binary @"$FILE"
curl -sS -X POST "https://chorus.host$(jq -r .version.finalizeUrl site.json)" \
-H "X-Claim-Token: $(jq -r .claimToken site.json)"
jq -r '"\nLive: \(.site.url)\nClaim it to keep it: \(.claimUrl)"' site.json
To choose the name, add "slug":"my-site" next to "files". Names are 3 to 63 lowercase letters, digits and hyphens.
A whole folder with curl
publish-dir.sh: the same three calls for every file in a folder (bash, curl, jq)
Save this as publish-dir.sh and run bash publish-dir.sh ./site. It sends every file in the folder (hidden files are skipped) and prints the URL. If BEACON_API_KEY is set, the site goes into your account instead of expiring.
#!/usr/bin/env bash
# publish-dir.sh DIR: publish a folder to chorus.host and print its URL.
# No account: the site lasts 24 hours. With BEACON_API_KEY set: it's yours.
# Needs curl, jq, and shasum or sha256sum. Files up to 250 MB each.
set -eo pipefail
cd "${1:?usage: publish-dir.sh DIR}"
mime() {
case "$(printf '%s' "${1##*.}" | tr '[:upper:]' '[:lower:]')" in
html|htm) echo "text/html; charset=utf-8" ;;
css) echo "text/css; charset=utf-8" ;;
js|mjs) echo "text/javascript; charset=utf-8" ;;
json|map) echo "application/json" ;;
txt) echo "text/plain; charset=utf-8" ;;
csv) echo "text/csv; charset=utf-8" ;;
xml) echo "application/xml" ;;
svg) echo "image/svg+xml" ;;
png) echo "image/png" ;;
jpg|jpeg) echo "image/jpeg" ;;
gif) echo "image/gif" ;;
webp) echo "image/webp" ;;
ico) echo "image/x-icon" ;;
pdf) echo "application/pdf" ;;
woff2) echo "font/woff2" ;;
wasm) echo "application/wasm" ;;
mp4) echo "video/mp4" ;;
*) echo "application/octet-stream" ;;
esac
}
sha256() { { shasum -a 256 "$1" 2>/dev/null || sha256sum "$1"; } | cut -d' ' -f1; }
manifest=$(find . -type f ! -path '*/.*' | sed 's|^\./||' | while IFS= read -r file; do
jq -n --arg path "$file" --argjson size "$(wc -c < "$file" | tr -d ' ')" \
--arg type "$(mime "$file")" --arg hash "sha256:$(sha256 "$file")" \
'{path: $path, size: $size, contentType: $type, hash: $hash}'
done | jq -s '{files: .}')
auth=()
if [ -n "$BEACON_API_KEY" ]; then
auth=(-H "Authorization: Bearer $BEACON_API_KEY")
fi
res=$(curl -sS --fail-with-body https://chorus.host/v1/sites "${auth[@]}" \
-H "Content-Type: application/json" -d "$manifest")
jq -r '(.uploads.pending // [])[] | select(.uploadUrl) | [.path, .uploadUrl] | @tsv' <<<"$res" |
while IFS=$'\t' read -r file url; do
curl -sS --fail-with-body -X PUT "$url" -H "Content-Type: $(mime "$file")" \
--data-binary @"$file" >/dev/null
done
token=$(jq -r '.claimToken // empty' <<<"$res")
if [ -n "$token" ]; then
auth=(-H "X-Claim-Token: $token")
fi
curl -sS --fail-with-body -X POST "https://chorus.host$(jq -r .version.finalizeUrl <<<"$res")" \
"${auth[@]}" >/dev/null
jq -r '"Live: \(.site.url)", (.claimUrl // empty | "Claim it to keep it: \(.)")' <<<"$res"
The beacon CLI
curl -fsSL https://chorus.host/install.sh -o install-beacon.sh && sh install-beacon.sh
beacon deploy ./site --pretty
On Windows PowerShell, install with iwr https://chorus.host/install.ps1 -useb | iex and run the same beacon deploy. If the folder has a package.json with a build script, the CLI runs npm run build first and uploads dist/, build/ or out/. It saves the claim token in .beacon/state.json, so after beacon login, running beacon deploy again from the same place claims the site.
Claim the site later
Every site published without an account comes with a claimUrl like https://chorus.host/claim/navy-twig-20#ctk_....
- Treat it like a password. Whoever opens it can take the site. Don't post it publicly.
- How a person claims: open the link, enter an email address, enter the 6-digit code we send. There's no password to set.
- Any device works. The claim code is in the part of the link after
#, which browsers never send to a server, so the link can be opened on a phone or forwarded to a colleague. - With an API key:
curl -sS -X POST "https://chorus.host/v1/sites/$SLUG/claim" \
-H "Authorization: Bearer $CHORUS_API_KEY" -H "Content-Type: application/json" \
-d '{"claimToken":"'"$TOKEN"'"}'
- With the tools: run
beacon login, thenbeacon deployfrom the same folder; or setCHORUS_API_KEYand runpublish.pyagain. Both claim the saved site automatically. - If nobody claims it: after 24 hours the site is deleted, its URL shows a "publish yours" page, and the claim link stops working. Deleted sites can't be restored.
Claiming removes the expiry. After that, manage the site with Authorization: Bearer instead of X-Claim-Token.
No account vs free account
| No account | Free account | |
|---|---|---|
| How long the site lasts | 24 hours, then it is deleted (how expiry works) | Until you delete it, or an expiry you set |
| Publishes per hour | 5 per IP address | 60 per account |
| Largest file | 250 MB | 5 GB |
| Files per site | 10,000 | 10,000 |
| Total size per site | 10 GB | 10 GB |
| Password protection | Yes | Yes |
| Search engines | Sent X-Robots-Tag: noindex until claimed | Can be indexed |
| Custom domain | No | No |
| Workers (APIs, webhooks) | No | Yes |
Custom domains aren't available on any plan yet. Every site lives at <name>.chorus.host.
Other ways to publish without an account
Checked 1 October 2026:
- Netlify: Netlify Drop and
netlify deploy --allow-anonymouswork with no account, but the site is password-protected until claimed and removed after 1 hour if nobody claims it. - Cloudflare:
npx wrangler deploy --temporary(and Cloudflare Drop in the browser) gives a publicworkers.devlink with 60 minutes to claim it. - surge.sh and tiiny.host: both need an account; surge creates one in the terminal on first run.
Netlify Drop, tiiny.host and surge.sh compared in full, with measured times, and how chorus.host compares with here.now.
When another host fits better:
- A custom domain: claim on Netlify or Cloudflare, or use here.now's free plan, which includes one custom domain.
- Framework builds on every push: Vercel or Netlify connected to your git repo.
- A link that should outlive a day with no account: ship.page keeps anonymous drops for 30 days.
- A Worker with KV or D1:
wrangler deploy --temporaryincludes them; chorus.host Workers have no built-in storage.
Need an API next to the site? Deploy it as a chorus.host Worker. Sharing one HTML report? Share an HTML file as a link has the password, update and expiry steps.
Good to know
- A path that doesn't match a file returns 404. There is no fallback to
index.html, so single-page apps need hash routing or one HTML file per route. - A folder URL serves that folder's
index.html. A folder without one shows a file list. - Files are served with the content type you declare and
X-Content-Type-Options: nosniff, so a.cssfile declared astext/plainwon't load as a stylesheet. - Executables and installers (
.exe,.msi,.dmg,.jarand similar) and shell-script content types are rejected.
Questions
Can I host a static site without an account?
Yes. curl -fsSL https://chorus.host/publish.py | python3 - ./site publishes the folder to https://<name>.chorus.host with no account. It stays up for 24 hours; open the claim link and sign in to keep it.
Is there free static hosting with no signup?
chorus.host is free with or without an account. Without one, sites last 24 hours and you can publish 5 an hour from one IP address. A free account, made with just an emailed code, keeps sites until you delete them.
What's the fastest way to put a single index.html on a public URL?
curl -sS https://chorus.host/v1/publish -F file=@index.html. One request, no account, and the response has the link.
How do I deploy a static site from the command line without signing up?
Run curl -fsSL https://chorus.host/publish.py | python3 - ./dist from your project, or install the beacon CLI and run beacon deploy ./dist. Both print the URL. Running either again updates the same site.
How long does a site published without an account stay up?
24 hours from the first publish. Updating it doesn't reset the clock. Claim it to keep it, or it is deleted and the URL stops working.
How do I claim a site later, and can my AI agent hand it to me?
Open the claim link the publish returned and sign in with your email and a 6-digit code. Agents using the chorus.host skill or MCP server are told to give you that link exactly as returned, so you can claim the site from any device.
Can I use a custom domain?
No. Every site lives at <name>.chorus.host, with or without an account. If you need your own domain, Netlify, Cloudflare Pages or here.now's free plan support one.
Is there an instant static hosting API with no signup?
Yes. POST https://chorus.host/v1/publish takes a file, a multipart form or a zip and returns the live URL in one request. For big sites, the three-call API (POST /v1/sites, upload, finalize) handles files up to 250 MB each.
Will search engines index my site?
Not while it's unclaimed: those sites are sent with X-Robots-Tag: noindex. Once claimed into an account, they can be indexed like any other site.