# Upload a file, get a public link that opens in the browser

chorus.host turns a PDF, image, HTML file or folder into a public HTTPS link at `<name>.chorus.host`. No account or API key; links without an account last 24 hours, and files are served with their real content type, so PDFs and images open in the browser instead of downloading.

## How do I upload a file from the command line?

```bash
curl -sS https://chorus.host/v1/upload -F file=@report.pdf
# https://bright-river-42.chorus.host/report.pdf
```

`/v1/upload` prints only the link, like `0x0.st` and `transfer.sh` did. The claim details come back in response headers (`X-Claim-Token`, `X-Claim-Url`, `X-Expires-At`); add `-D -` to see them. The transfer.sh style works too:

```bash
curl -sS -T chart.png https://chorus.host/v1/upload/chart.png
```

For the full JSON response, post to `/v1/publish` instead:

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

```json
{
  "url": "https://bright-river-42.chorus.host",
  "fileUrl": "https://bright-river-42.chorus.host/report.pdf",
  "slug": "bright-river-42",
  "anonymous": true,
  "expiresAt": "2026-10-02T09:14:03Z",
  "claimUrl": "https://chorus.host/claim/bright-river-42#ctk_REDACTED",
  "claimToken": "ctk_REDACTED"
}
```

- `fileUrl` is the direct link to the file. Paste that one into chat apps so images and PDFs preview.
- `url` is the site root. For a single non-HTML file it shows a small viewer page (the PDF in a frame, the image centered).
- A single HTML file becomes the site's home page, so `url` opens it.

Python instead of curl, for a file or a folder: `curl -fsSL https://chorus.host/publish.py | python3 - report.pdf`. It reads `CHORUS_API_KEY` or `~/.config/beacon/config.json` if you have them, and then the link is permanent instead of 24 hours.

## How do I share a PDF and a PNG as public links?

Send both in one request; each keeps its name:

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

The response lists `fileUrls`: `https://<name>.chorus.host/report.pdf` and `.../chart.png`. What a browser gets for each (checked against chorus.host on 1 October 2026):

| File | Content-Type | Content-Disposition | What the browser does |
|---|---|---|---|
| `report.pdf` | `application/pdf` | none | Opens it in the PDF viewer |
| `chart.png` | `image/png` | none | Shows the image; chat apps unfurl the direct link |
| `notes.html` | `text/html; charset=utf-8` | none | Renders it as a page |

Every file is also sent with `X-Content-Type-Options: nosniff`, so browsers trust the declared type instead of guessing.

## Call the API directly (no account)

The upload endpoints take up to 4 MB per request without an account (10 MB with an API key). For bigger files, up to 250 MB each without an account, use the three-call API: register the file with its size and SHA-256 hash, upload it to the presigned URL you get back, then finalize.

```bash
F=report.pdf
HASH=$( (shasum -a 256 "$F" 2>/dev/null || sha256sum "$F") | cut -d' ' -f1)
SIZE=$(wc -c < "$F" | tr -d ' ')
curl -sS https://chorus.host/v1/sites -H 'Content-Type: application/json' \
  -d '{"files":[{"path":"report.pdf","size":'"$SIZE"',"contentType":"application/pdf","hash":"sha256:'"$HASH"'"}]}' > site.json
curl -sS -X PUT "$(jq -r '.uploads.pending[0].uploadUrl' site.json)" \
  -H 'Content-Type: application/pdf' --data-binary @"$F"
curl -sS -X POST "https://chorus.host$(jq -r .version.finalizeUrl site.json)" \
  -H "X-Claim-Token: $(jq -r .claimToken site.json)" > /dev/null
echo "$(jq -r .site.url site.json)/report.pdf"
```

Delete it when you're done:

```bash
curl -sS -X DELETE "https://chorus.host/v1/sites/$(jq -r .site.slug site.json)" \
  -H "X-Claim-Token: $(jq -r .claimToken site.json)"
```

Full reference: [API docs](/docs).

## From an agent: MCP and skill

- **MCP:** `claude mcp add --transport http chorus https://chorus.host/mcp`. The `publish_site` tool takes files as text, or base64 with `encoding: "base64"` for PDFs and images, up to 4 MB per call without an API key. It returns `file_url` when you publish one file. [Other clients](/mcp).
- **Skill:** `npx skills add chorus-host/skill -g` teaches Claude Code, Codex, Cursor and Gemini CLI to publish files and folders and hand back the link.

## Limits

| | No account | Free account |
|---|---|---|
| How long a link lasts | 24 hours | Until you delete it, or an expiry you set |
| Largest file | 250 MB (4 MB through `/v1/upload` and `/v1/publish`) | 5 GB (10 MB through `/v1/upload` and `/v1/publish`) |
| Publishes per hour | 5 per IP address | 60 per account |
| Search engines | Sent `X-Robots-Tag: noindex` | Can be indexed |

Rate limits are per IP address and per account, so uploads from CI runners and cloud machines work as long as they stay under the budget. Executables, installers, scripts, disk images and credential files are refused. Current numbers: [`GET /v1/limits`](/v1/limits).

## Replacing 0x0.st, catbox, litterbox or transfer.sh

Checked 1 October 2026 from each service's own pages. Where a fact couldn't be confirmed, it's left out.

| | Account needed | Largest file | How long files stay | HTML files | Automated use | Status on 1 Oct 2026 |
|---|---|---|---|---|---|---|
| chorus.host | No | 250 MB (4 MB in one request) | 24 hours without an account; permanent with a free one | Rendered as pages | Allowed, within rate limits | Up |
| [0x0.st](https://0x0.st) | No | 512 MiB | 30 days to 1 year, by size | | Terms exclude AI-generated content, CI build artifacts and automated mass uploads | Not reachable from our network |
| [catbox.moe](https://catbox.moe/faq.php) | No | 200 MB | Permanent | Served as plain text | No commercial or CDN use without approval | Up |
| [litterbox](https://litterbox.catbox.moe/) | No | 1 GB | 1, 12, 24 or 72 hours | | | Up |
| [transfer.sh](https://github.com/dutchcoders/transfer.sh) | No | | | | | The public instance refused connections; the maintainer recommends self-hosting |

Where they're better: catbox keeps files permanently for free, up to 200 MB. litterbox takes files up to 1 GB for up to 72 hours. 0x0.st keeps files for up to a year. A link from chorus.host without an account lasts 24 hours, and you can create 5 an hour from one IP address.

Switching a script over:

```bash
# before
curl -F 'file=@build.log' https://0x0.st
curl -F 'reqtype=fileupload' -F 'fileToUpload=@chart.png' https://catbox.moe/user/api.php
curl --upload-file report.pdf https://transfer.sh/report.pdf

# after
curl -sS -F 'file=@build.log' https://chorus.host/v1/upload
curl -sS -F 'file=@chart.png' https://chorus.host/v1/upload
curl -sS --upload-file report.pdf https://chorus.host/v1/upload/report.pdf
```

## What you can't upload

Phishing, malware, credential harvesting, spam and anything illegal are banned ([terms](/terms)). Executables, installers, scripts and disk images are refused at upload, and so are files that usually hold secrets (`.env`, `.npmrc`, SSH keys). Sites without an account aren't indexed by search engines and are deleted after 24 hours. Every viewer page has a report link; report abuse to [abuse@chorus.host](mailto:abuse@chorus.host).

## Questions

### Do I need an API key?

No. `curl -sS https://chorus.host/v1/upload -F file=@report.pdf` works without one. An API key raises the limits and makes links permanent.

### How long does the link last, and can I make it permanent?

24 hours without an account. Open the claim link (`X-Claim-Url` header, or `claimUrl` in the JSON) and sign in with your email to keep it, or upload with an API key from the start.

### Why do other hosts download my HTML instead of rendering it?

They serve it as `text/plain`, or add `Content-Disposition: attachment`, so the browser shows the source or saves the file. Some do it on purpose to stop phishing pages. chorus.host serves HTML as `text/html` and relies on its upload checks, abuse reports and 24-hour expiry instead.

### Can I password-protect the link?

Yes, free: add `-F username=client -F password='...'` to a `/v1/publish` request. See [password-protect an HTML page](/guides/password-protect-html-page).

### What is the size limit?

4 MB per request through `/v1/upload` and `/v1/publish` without an account, 10 MB with an API key. Through the three-call API: 250 MB per file without an account and 5 GB with one.

### Can my CI job use it?

Yes. Rate limits are per IP address (5 an hour without an account) or per account (60 an hour), so a CI job that uploads often should use an API key: `-H "Authorization: Bearer $CHORUS_API_KEY"`.

## Related

- [Share an HTML file as a link](/guides/share-html-file-as-link): HTML-specific steps, including passwords and updates.
- [Host a static site with no signup](/guides/host-static-site-no-signup): folders, the CLI and claiming.
- [chorus.host vs here.now](/vs/here-now)
