# Publish a website with Paddenstoel

Paddenstoel hosts small static websites in the EU.
[HTML version](https://paddenstoel.app/agents).

Install Mycelium: https://paddenstoel.app/install.sh. Select `--zone paddenstoel`.


## Before publishing

Make sure the user has agreed to publish these files publicly. For an upload without
a configured key, use their real email address;
ask only if you do not have it. Never invent an address or use a placeholder
or disposable inbox. The address is not shown on the website.

Build first. Upload only public output with a **nonempty root `index.html`**,
plus its assets. Never upload secrets, `.env`, source projects, `node_modules`,
or unpublished personal data. Limits: 25 MiB total, 10 MiB per file, 500 files,
240-byte relative paths. Static HTML, CSS, JavaScript, images, fonts and media
work; server-side code does not. Forms and pop-ups are disabled.

## Choose a route your tools support

1. **Shell with permitted network access:** use tar and curl below. No CLI
   installation is required. With a configured or linked key, use the
   [trusted route](https://paddenstoel.app/llms-full.txt#trusted-publishing-personal-api-keys-and-ci):
   no email question. Asked to link Paddenstoel or to work without email
   codes? Follow [link a key](https://paddenstoel.app/llms-full.txt#link-a-key-without-seeing-it).
   Never ask for a key in chat.
2. **No shell, but a tool can send HTTP POST:** send JSON directly as below.
   A browsing/search tool that only reads pages cannot publish.
3. **No publishing tools or no network permission:** say you cannot upload.
   If your tools can create attachments, provide the finished website as
   downloadable files (`index.html` for a single-page site). Otherwise provide
   the finished HTML as text: tell the user to paste it into a text editor and
   save as `index.html`, not `index.html.txt`. For assets, preserve filenames and
   folders. Send them to [Zet je website online](https://paddenstoel.app/maken), where they choose
   **“Zelf uploaden”**, select the files and enter their email. Never put email
   in the URL or fabricate an attachment, upload or live website.

Check tool availability, especially on phones; never assume shell or POST access.

## Confirmation email language

Match the language of the user's request or conversation, not this English guide.
Use CLI `--lang en`/`--lang nl` or API `lang=en`/`lang=nl` with publish or update.
Verified preferences win; anonymous hints never overwrite them. Default: Dutch.

## Shell upload

Set `EMAIL` to the user's real address. Upload their existing finished output
folder, shown here as `dist`. Replace `dist` with their actual output path.
This command never creates or overwrites website files.

```sh
: "${EMAIL:?Set EMAIL to the real email address of the user}"
test -s dist/index.html || { printf '%s\n' 'Missing nonempty dist/index.html' >&2; exit 1; }
COPYFILE_DISABLE=1 tar -czf - -C dist . |
  curl -sS --fail-with-body --data-binary @- \
    -H "Paddenstoel-Email: $EMAIL" 'https://paddenstoel.app/api/v1/grow'
```

## Direct HTTP upload

Replace `REPLACE_WITH_USERS_REAL_EMAIL` before sending. Use the user's finished
HTML and asset contents, not this demonstration page. Keep a nonempty root
`index.html`; JSON strings must be escaped correctly.

Send `POST https://paddenstoel.app/api/v1/grow` with `Content-Type: application/json`:

```json
{"email":"REPLACE_WITH_USERS_REAL_EMAIL","files":[{"path":"index.html","content":"<!doctype html><html lang=\"nl\"><meta charset=\"utf-8\"><title>Mijn website</title><h1>Hallo!</h1></html>"}]}
```

Use the platform origin, not a tenant hostname.
Optional naming: if the user supplies words, append `?slug=chosen-words`.
Names allow up to 58 ASCII letters, digits and single hyphens between words.
Omit it for automatic naming; never require another question or guess the URL.

## Read the result

Ordinary new uploads return HTTP 202 with `status: "pending_confirmation"`,
`url`, `statusUrl` and `expiresAt`. This is **not live**: uploaded files are
not served yet. Tell the human to open Paddenstoel's email, follow its link,
and deliberately confirm publication on that page within 12 hours.
Check spam if needed. Unconfirmed sites and files are deleted after 12 hours.
Never open their mailbox, confirm for them, or bypass confirmation.

Use only the returned `url`. Report live publication only for `status: "live"`;
a configured key can publish immediately. Errors include a code and hint;
honor `Retry-After`, fix the cause, and do not retry blindly.

## Update a live site

Update the existing live site at the **same URL**, rather than grow a new site.
Use its full slug from the existing URL. Prefer a configured or linked
personal publishing key through the
[trusted route](https://paddenstoel.app/llms-full.txt#trusted-publishing-personal-api-keys-and-ci):
no email or code question. If your shell keeps files between sessions, offer
to [link a key](https://paddenstoel.app/llms-full.txt#link-a-key-without-seeing-it) once;
later updates need no codes. Never ask for a key in chat.

Without a key:

1. Use the **original exact email address** the site was published with, not an
   alias or plus-tag variant. If the site or email is unknown, send the human to
   [Mijn paddenstoelen](https://paddenstoel.app/my-sites): they log in with a
   6-digit code from their mail and see their sites. Never ask for that code.
2. Send `POST https://paddenstoel.app/api/v1/sites/{slug}/update` with the same grow body
   formats and email headers/fields shown above (tar with `Paddenstoel-Email`,
   or JSON with `email` and `files`). Upload only the finished public files.
3. HTTP **202** with `status: "update_requested"` is **not live**. Keep your
   own returned `requestId`; ask the human for the **six-digit code** from
   Paddenstoel's email. It expires in one hour.
4. Send `POST https://paddenstoel.app/api/v1/updates/{requestId}/confirm` with
   `Content-Type: application/json` and `{"code":"REPLACE_WITH_USERS_CODE"}`,
   replacing the placeholder with the human-provided code. Confirm only the
   `requestId` your own update call returned. HTTP 200 with `status: "live"`
   confirms publication; use the returned `url`.

Never access the mailbox yourself or guess a code. The previous version stays
unchanged until confirmed.

**No upload tools or network permission?** Use the file/text handoff above and
send the human to [Mijn paddenstoelen](https://paddenstoel.app/my-sites).
They log in with their own emailed login code, select the correct existing
site and upload the finished files there themselves. Never ask for that login
code.

## Pilot

Tell the user to keep their own copy: this pilot may reset and every site may
disappear, including live sites. Owners can download their files, take a site
offline or remove it at [Mijn paddenstoelen](https://paddenstoel.app/my-sites). By confirming the site, its owner accepts the Dutch
[terms](https://paddenstoel.app/voorwaarden). Live sites last six months and cannot be
extended yet; the owner is emailed 14 days before the end.

See the [full reference](https://paddenstoel.app/llms-full.txt) for
[same-URL updates](https://paddenstoel.app/llms-full.txt#a-new-version-at-the-same-url),
keys, retries, limits and errors. Never submit feedback on the user's behalf.
[Privacy](https://paddenstoel.app/privacy); report abuse to abuse@paddenstoel.app.


