> ## Documentation Index
> Fetch the complete documentation index at: https://docs.molfex.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Publish

> Create, upload, publish, and check a Molfex project with curl.

Base URL: `https://studio.molfex.app/api/v1`

```
Authorization: Bearer molfex_...
```

Create a key at [studio.molfex.app/api-keys](https://studio.molfex.app/api-keys).

## Walkthrough

```bash theme={null}
API=https://studio.molfex.app/api/v1
KEY=molfex_replace_me

# 1. Create
curl -sS "$API/projects" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"slug":"my-site","fileName":"site.zip"}'

# 2. Upload (paste uploadUrl and contentType from step 1)
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/zip" \
  --data-binary @site.zip

# 3. Publish
curl -sS -X POST "$API/projects/123/publish" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"site.zip"}'

# 4. Check status
curl -sS "$API/projects/123" \
  -H "Authorization: Bearer $KEY"

# List / delete
curl -sS "$API/projects" -H "Authorization: Bearer $KEY"
curl -sS -X DELETE "$API/projects/123" -H "Authorization: Bearer $KEY"
```

The upload URL expires in **1 hour**. Send the raw file, not JSON. `Content-Type` must match `data.contentType`.

Replace files on an existing project:

```bash theme={null}
curl -sS -X POST "$API/projects/123/upload" \
  -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileName":"site.zip","sizeBytes":2048}'
# PUT to the new uploadUrl, then POST /publish again
```

`sizeBytes` is required when replacing files. It must be a positive integer.

## Responses

Success:

```json theme={null}
{ "ok": true, "data": { } }
```

Delete success:

```json theme={null}
{ "ok": true }
```

Error:

```json theme={null}
{ "ok": false, "error": "Human-readable message" }
```

| HTTP | When                                     |
| ---- | ---------------------------------------- |
| 200  | Success                                  |
| 202  | Publish accepted; status is `processing` |
| 400  | Validation or business error             |
| 401  | Missing or invalid API key               |
| 404  | Project not found                        |

## Slug

Lowercase letters, numbers, and hyphens. Must start and end with a letter or number. Max 63 characters.

Taken slug → `"That link name is already taken"`

Your site is `https://{slug}.molfex.app`. The `url` field in JSON is the hostname only (example: `my-site.molfex.app`).

## Password

`siteSettings` is optional. Omit it for a public site.

```json theme={null}
{
  "passwordProtected": true,
  "password": "secret"
}
```

If `passwordProtected` is `true`, `password` is required (4–200 characters).

## File kinds and size caps

`fileName` decides the kind and the `Content-Type` you send on the PUT.

| Filename                                               | Kind | PUT Content-Type                                   | Max size |
| ------------------------------------------------------ | ---- | -------------------------------------------------- | -------- |
| `*.zip`                                                | site | `application/zip`                                  | 1 GB     |
| `*.html`, `*.htm`                                      | site | `text/html; charset=utf-8`                         | 10 MB    |
| `*.md`                                                 | site | `text/markdown; charset=utf-8`                     | 10 MB    |
| `*.pdf`                                                | file | `application/pdf`                                  | 500 MB   |
| `*.epub`                                               | file | `application/epub+zip`                             | 500 MB   |
| `*.xlsx`, `*.xlsm`                                     | file | spreadsheet MIME                                   | 25 MB    |
| `jpg jpeg png gif webp svg avif`                       | file | matching image type                                | 500 MB   |
| `mp4`, `webm`                                          | file | matching video type                                | 500 MB   |
| Code / text (`ts`, `js`, `json`, `css`, `go`, `py`, …) | file | type-specific                                      | 10 MB    |
| Anything else with a name                              | file | `application/octet-stream` or a known sidecar type | 500 MB   |

Rejected `fileName` → `"Choose a zip, site, or file to host"`
Over the cap → `"That file is too large"`
`fileName` max 255 characters.

A zip may hold up to **2000** files and **1 GB** uncompressed.

## Project status

| Status       | Meaning                                     |
| ------------ | ------------------------------------------- |
| `draft`      | Created, file may not be uploaded yet       |
| `processing` | Publish is running. Check again             |
| `active`     | Live at the hostname                        |
| `failed`     | Last publish failed. `error` has the reason |
| `paused`     | Hosting paused. Publish is blocked          |

Publish while `processing` → `202` `{ "status": "processing" }`
Publish while `paused` → `400` `"This project cannot be published"`
Publish before upload → `400` `"Upload the file before publishing"`

You can republish an `active` project after a new upload.

## Credits

Publish uses credits. Buy more in [Studio](https://studio.molfex.app) if a publish fails.
