# Share an HTML file or report as a link

Turn `report.html` into a link like `https://bright-river-42.chorus.host` with one curl request. It opens as a web page, not a download. No account needed; the link lasts 24 hours unless you keep it, and you can add a password for free.

## Share an HTML file with one command

```bash
curl -sS https://chorus.host/v1/publish -F file=@report.html
```

That's the whole thing. A single HTML file is published as the site's home page, whatever it was called, so the link opens the report itself. Two other ways to send the same file:

```bash
# raw body instead of a form
curl -sS https://chorus.host/v1/publish -H 'Content-Type: text/html' --data-binary @report.html

# just the link on stdout (handy in scripts)
curl -sS https://chorus.host/v1/publish -H 'Accept: text/plain' -F file=@report.html
```

Want a name instead of a random one? Add `-F slug=q3-report` and the link is `https://q3-report.chorus.host`.

If your report links to local files (images, a CSV, a script), send them in the same request: repeat `-F file=@...` once per file, or zip the folder and send `-F archive=@report.zip`. Charts, fonts and styles loaded from a CDN work as they are.

No terminal? [Drop the file on chorus.host/html-to-url](/html-to-url) or paste the HTML there.

## What you get back

```json
{
  "url": "https://q3-report.chorus.host",
  "pageUrl": "https://q3-report.chorus.host/",
  "fileUrl": "https://q3-report.chorus.host/",
  "slug": "q3-report",
  "updated": false,
  "anonymous": true,
  "expiresAt": "2026-10-02T09:14:03Z",
  "claimUrl": "https://chorus.host/claim/q3-report#ctk_REDACTED",
  "claimToken": "ctk_REDACTED",
  "passwordProtected": false,
  "versionId": "01a0f5f1-41bc-79e6-b254-af0bb753695e",
  "fileCount": 1,
  "totalBytes": 182044
}
```

| Field | What it's for |
|---|---|
| `url` | The link to share. |
| `claimUrl` | Open it and sign in with your email to keep the report past 24 hours. Treat it like a password: whoever has it can take the site. |
| `claimToken` | Lets you update, password-protect or delete the report later. Keep it with the file; never put it on the page. |
| `expiresAt` | When the report is deleted if nobody claims it. |

Reports published without an account are sent with `X-Robots-Tag: noindex`, so search engines don't list them.

## Opens in the browser, not as a download

Plenty of places will store an HTML file and hand back a link that shows the source code or downloads the file. That happens when the server sends it as `text/plain` or with `Content-Disposition: attachment`, or when the file isn't the page the link points at.

chorus.host serves `.html` as `text/html; charset=utf-8`, never adds `Content-Disposition`, and makes a lone HTML file the home page. Check any link with:

```bash
curl -sI https://q3-report.chorus.host | grep -i content-type
# content-type: text/html; charset=utf-8
```

## Put a password on it

Free, even without an account. Add a username and password to the same request:

```bash
curl -sS https://chorus.host/v1/publish -F file=@report.html \
  -F username=team -F password='choose-a-password'
```

Or lock a report you already published, using its claim token:

```bash
curl -sS -X PUT https://chorus.host/v1/sites/q3-report/password \
  -H "X-Claim-Token: $TOKEN" -H 'Content-Type: application/json' \
  -d '{"username":"team","password":"choose-a-password"}'
```

Visitors get the browser's login prompt before any byte of the report is sent. More detail, and how this compares with other hosts: [password-protect an HTML page](/guides/password-protect-html-page).

## Update it and keep the same link

Send the new file to the same slug with the claim token:

```bash
curl -sS "https://chorus.host/v1/publish?slug=q3-report" \
  -H "X-Claim-Token: $TOKEN" -F file=@report.html
```

The link stays the same. Earlier versions are kept, and `POST /v1/sites/<slug>/versions/<versionId>/rollback` brings one back. Updating doesn't reset the 24-hour clock on a report without an account.

## Keep it, or have it expire on purpose

- **Keep it:** open the `claimUrl` and sign in with the code we email you. No password, no expiry after that.
- **Expire sooner:** add `-F expires=2h` (or `90m`, or an RFC 3339 time). Without an account it can be any time up to 24 hours after the first publish, never later.
- **Expire later, on a schedule:** publish with an API key (`-H "Authorization: Bearer $CHORUS_API_KEY"`) and set any future time, for example `-F expires=7d`.

How expiry works: [temporary website hosting](/guides/temporary-website-hosting).

## Take it down

```bash
curl -sS -X DELETE https://chorus.host/v1/sites/q3-report -H "X-Claim-Token: $TOKEN"
```

For a report in your account, send `Authorization: Bearer $CHORUS_API_KEY` instead of `X-Claim-Token`. The link returns 404 right away.

## Large files, folders and CI: the three-call API

`/v1/publish` takes up to 4 MB of files per request without an account (10 MB with an API key). For bigger reports, or a pipeline that uploads only what changed, use the three-call API: register the files with their sizes and SHA-256 hashes, upload each one to the URL you get back, then finalize. It takes files up to 250 MB each without an account.

<details>
<summary>The three calls for one file</summary>

```bash
FILE=report.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
```

</details>

The hash is what lets chorus.host skip files it already has, so re-publishing a big folder only uploads what changed. Folders: `curl -fsSL https://chorus.host/publish.py | python3 - ./report-folder` does all three calls for you.

## Measured

On 1 October 2026 we published a 180 KB report five times with the three-call API from a Mac in the San Francisco Bay Area. The median time from the first request to the link answering `200 text/html` was 2.1 seconds. The one-request endpoint does the same work on the server, in one round trip from your side.

## Other ways to share one HTML file

Checked 1 October 2026 against each service's own docs and pricing pages. Plans change; follow the source links before you rely on a cell.

| | Account to publish | From a script | Link without paying | Password on the free plan | Opens as a page |
|---|---|---|---|---|---|
| chorus.host | No | One request | 24 hours, or until you delete it once claimed | Yes | Yes |
| [Netlify Drop](https://docs.netlify.com/start/quickstarts/netlify-drop-quickstart/) | No | `netlify deploy --allow-anonymous` | Removed after 1 hour unless claimed; until claimed it sits behind a temporary password | No: [site passwords need Pro](https://docs.netlify.com/manage/security/secure-access-to-sites/password-protection/) | Yes |
| [GitHub Pages](https://docs.github.com/en/pages) | Yes | `git push` to a Pages repo | Permanent | No | Yes |
| [tiiny.host](https://tiiny.host/pricing) | Yes (email sign-up) | API on paid plans | While you log in at least every 3 months | No: from the Solo plan | Yes |
| [surge.sh](https://surge.sh/docs/getting-started) | Yes (created in the terminal on first run) | `surge ./site publish` | Permanent | No: Surge Professional | Yes |

Where the others fit better: GitHub Pages keeps a free site up indefinitely and versions it in git. Netlify gives you a custom domain and a full build pipeline once you claim the site. tiiny.host has an editor, analytics and QR codes on its paid plans. chorus.host is built for the moment a script or an agent needs a working link with no account.

## Let the agent do it

Paste this into Claude Code, Codex, Cursor or any agent that can run commands:

```text
Publish report.html to chorus.host and give me the link and the claim link. Instructions: https://chorus.host/skill.md
```

Agents without a shell (claude.ai, Cursor's chat, other MCP clients) can use the MCP server instead: `claude mcp add --transport http chorus https://chorus.host/mcp`. Setup for every client is on [chorus.host/mcp](/mcp).

## Questions

### How do I share an HTML file as a link?

Run `curl -sS https://chorus.host/v1/publish -F file=@report.html`. You get back `https://<name>.chorus.host`, which opens the file as a page. No account needed; the link lasts 24 hours unless you claim it.

### Is there a service where I can curl an HTML file and get a shareable link back?

Yes. `curl -sS https://chorus.host/v1/publish -F file=@report.html` is one request and returns JSON with the link. Add `-H 'Accept: text/plain'` to get only the link.

### Can an AI agent share an HTML report as a link for me?

Yes. Ask it to publish the report to chorus.host and point it at https://chorus.host/skill.md, or add the MCP server at https://chorus.host/mcp. It returns the link and a claim link you can open to keep the report.

### Will the link open the page or download the file?

It opens the page. HTML is served as `text/html` with no download header, and a single HTML file becomes the home page of the link.

### Who can see it, and will Google index it?

Anyone with the link, unless you add a password. Reports published without an account are sent with `X-Robots-Tag: noindex`, so search engines leave them out.

### How long does the link last?

24 hours without an account. Open the claim link and sign in to keep it, or publish with an API key and choose any expiry, including none.
