# Static sites

A space holds one static website. Each deploy uploads the whole site as one archive and publishes it atomically.

## Create a space

The slug is part of the permanent URL, so use the product's name: 1-30 characters, lowercase letters, digits and
single hyphens (`my-app`, not `My_App` or `my--app`).

```bash
curl -sS -X POST "$API/spaces" -H "$AUTH" -H "content-type: application/json" \
  -d '{"slug":"my-app","name":"My App"}' -w '\nHTTP %{http_code}\n'
```

```json
{"id":"spc_7t1v...","slug":"my-app","name":"My App","status":"active","active_deployment_id":null,
 "public_url":"https://heremyapp.com/peter/my-app","site_url":"https://peter--my-app.heremyapp.com/","created_at":"2026-10-08T06:15:13.492Z"}
```

`409 space_exists` means the space is already there: use it. `GET $API/spaces` lists your spaces.
Share `public_url`; `site_url` is the origin it redirects to and may change.

## Deploy

Put the built site in a folder with `index.html` at its root and send the folder as a tar.gz body:

```bash
tar -czf - -C dist . | curl -sS --fail-with-body -X POST "$API/spaces/my-app/deployments" \
  -H "$AUTH" -H "content-type: application/gzip" --data-binary @-
```

```json
{"id":"dep_5adj...","space":"peter/my-app","status":"published","version":"1.0.0","build":1,"notes":null,
 "file_count":12,"total_size":845120,
 "public_url":"https://heremyapp.com/peter/my-app","site_url":"https://peter--my-app.heremyapp.com/",
 "skipped":[".DS_Store"],"stripped_prefix":null,"created_at":"2026-10-08T06:15:15.476Z",
 "advice":[{"code":"missing_terms","message":"No terms of use at /terms. ...","docs":"https://heremyapp.com/docs/legal"}]}
```

- Every deploy gets the next build number and a version. Add `bump` (`major`, `minor` or `patch`) and `notes` to the
  deploy URL; see [Releases and versioning](https://heremyapp.com/docs/releases.md). Without `bump`, the patch version goes up.
- The response's `advice` list points out things people often miss (privacy policy, terms, link previews, page
  language, favicon). It never blocks a deploy; see [Privacy policy and terms](https://heremyapp.com/docs/legal.md#deploy-advice).
- Publishing is atomic. A failed deploy leaves the live site untouched.
- Redeploying keeps the same URL, and the change is visible immediately.
- The server detects tar or tar.gz from the bytes; send `content-type: application/gzip` or `application/x-tar`.
- If the archive wraps everything in a single folder (for example `dist/`), that folder is unwrapped.

## File rules

- `index.html` must exist at the site root.
- Allowed extensions: html, htm, css, js, mjs, json, map, webmanifest, txt, md, xml, pdf, wasm, png, jpg, jpeg, gif,
  webp, avif, svg, ico, woff, woff2, ttf, otf, mp4, webm.
- If any file has another extension, or no extension, the **whole deploy fails** with `unsupported_file_type`, and
  `details.files` lists the offending files. Remove or rename them and deploy again.
- Installers (.pkg, .dmg, .zip) are never site files. Publish them as [installers](https://heremyapp.com/docs/installers.md).
- Hidden files and folders (`.DS_Store`, `._*`, `.git/`) and `__MACOSX/` are skipped and listed in `skipped`.
- Paths with `..`, absolute paths, backslashes and duplicates are rejected (`invalid_path`, `duplicate_path`).
  Symlinks and other special files are rejected with `unsupported_entry`: copy the real files into the folder instead.
- Limits: 20 MiB of files in total, 1,000 files, 25 MiB compressed upload.

## How URLs map to files

The site is served at the root of its own origin, so relative (`assets/app.css`) and root-absolute
(`/assets/app.css`) paths both work.

| Request | File served |
|---|---|
| `/` | `index.html` |
| `/style.css` | `style.css` (exact path first) |
| `/about` | `about.html`, otherwise `about/index.html` (redirects to `/about/`) |
| `/about/` | `about/index.html` |
| anything missing | your `404.html` with status 404, if it exists |

## Verify

```bash
curl -sIL https://heremyapp.com/ACCOUNT/my-app | grep -iE "^HTTP|^location"   # 302 to the site origin, then 200
```

## Stage, inspect and roll back

- Stage without publishing: add `?publish=false` to the deploy URL, then publish it with
  `POST $API/deployments/DEPLOYMENT_ID/publish`.
- `GET $API/spaces/my-app/deployments` lists the 50 most recent deployments, newest first. The live one has
  `"status":"published"`.
- `GET $API/deployments/DEPLOYMENT_ID` shows one deployment with its file list.
- Roll back by publishing an older deployment:

```bash
curl -sS --fail-with-body -X POST "$API/deployments/OLD_DEPLOYMENT_ID/publish" -H "$AUTH"
```
