# Sites API

> List, show and create Falak sites via the REST API — every create field, domains and Docker Compose options — and read or replace env and Laravel toggles.

Source: https://falak.sh/docs/api/sites/

Sites are addressed by **id or slug** in every path (`{site}`).

## The site resource

```json
{"data": {
  "id": "01k…", "name": "Shop", "slug": "shop", "status": "ready",
  "framework": "laravel", "runtime": "frankenphp", "build_mode": "native",
  "php_version": "8.4", "node_version": null,
  "repository": "acme/shop", "branch": "main", "push_to_deploy": true,
  "domain": "shop.example.com", "url": "https://shop.example.com",
  "web_directory": "public", "root_path": "/srv/falak/sites/shop", "app_port": null, "test_domain": null,
  "server_ids": ["01k…"],
  "targets": [{"id": "…", "server_id": "…", "server_name": "web-1", "server_ip": "203.0.113.1",
               "role": "leader", "status": "ready", "status_message": null, "command_id": null}],
  "strategy": "zero-downtime",
  "current_release": {"id": "01k…", "commit": "a1b2…", "branch": "main", "deployment_id": "01k…", "active": true},
  "created_at": "2026-09-26T10:00:00+00:00"
}}
```

| Field | Values |
|---|---|
| `framework` | `laravel`, `symfony`, `statamic`, `wordpress`, `php`, `next`, `nuxt`, `node`, `static`, `docker` |
| `runtime` | `frankenphp`, `php-fpm`, `node`, `bun`, `deno`, `static`, `docker`, `compose` |
| `build_mode` | `native`, `docker` (`on-server` is not supported yet) |
| `targets[].role` | `leader` or member |
| `strategy`, `current_release` | Added by the Deployments module |

`GET /api/v1/sites/{site}` also returns `deploy_script`, `shared_paths` and `laravel`. Compose sites carry `compose` (below).

## `GET /api/v1/sites` — `sites.view`

All sites of the organization.

## `GET /api/v1/sites/{site}` — `sites.view`

One site.

## `POST /api/v1/sites` — `sites.create`

Same body and validation as the create form. Returns `201` with the site plus `warnings[]` from the git provider (for example when a webhook could not be created). Creating a site does **not** deploy it.

### Fields

| Field | Rule |
|---|---|
| `name` | Required; up to 64 characters, `^[A-Za-z0-9][A-Za-z0-9 ._-]*$`, unique in the organization |
| `slug` | Optional; `^[a-z0-9][a-z0-9-]{0,62}$`, unique; derived from the name when omitted |
| `framework` | Required unless `runtime` is `compose` (then default `docker`) |
| `runtime` | Optional; the preset's default otherwise |
| `build_mode` | Optional; the runtime's default otherwise |
| `server_ids[]` | Required; 1–50 server ids |
| `leader_server_id` | Optional; default the first server |
| `source_connection_id` | A git connection id |
| `repository` | Required with a connection; e.g. `acme/shop` or a URL for custom git |
| `branch` | Required with a repository |
| `push_to_deploy` | Boolean, default `false` |
| `php_version` | `7.4`–`8.5` |
| `node_version` | `18`, `20`, `22`, `24` |
| `web_directory` | Relative path, no `..` |
| `app_port` | 1024–65535 (allocated from 3000–3999 when omitted, for runtimes that need one) |
| `docker_image` | Image reference (Docker runtime) |
| `dockerfile` | Path in the repository (Docker builds) |
| `root_directory` | Git sites (also `PATCH`): the repository subfolder the app lives in, e.g. `apps/api`; relative, surrounding slashes trimmed, no `.`/`..` segments. Returned as `root_directory` (`null` = the repository root). See [Monorepos](/docs/deploy/monorepos/#root-directory). |
| `health_check_path` | Starts with `/` |
| `test_domain_enabled` | Boolean |
| `isolated` | Boolean: own Linux user for the site |
| `variables` | Object `{KEY: value}`, up to 500; initial environment; `${{ service.KEY }}` allowed |
| `domain` | A [domain choice](#domain-choices) (not for Compose sites) |
| `project_id`, `environment_id` | Placement; default: the Default project's `production` environment. An environment of another organization or project is a `422`. |
| `template` | `{slug, version, source: catalog\|custom}` |

### Domain choices

| Value | Result |
|---|---|
| `{"type": "generated"}` | `<slug>.<leader-ip-with-dashes>.sslip.io` (the organization's generated-domain suffix) |
| `{"type": "custom", "name": "shop.example.com"}` or `"shop.example.com"` | Your domain, with automatic TLS once DNS points at the server |
| `{"type": "test"}` | Only the test domain (`422` when none is configured) |

The chosen domain becomes the site's primary domain and `APP_URL` in the initial environment. A name used by another site is a `422`. Without `domain`, the site only gets its test domain.

### Example

```bash
curl -X POST https://falak.example.com/api/v1/sites \
  -H "Authorization: Bearer $FALAK_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{
    "name": "Shop",
    "framework": "laravel",
    "server_ids": ["01k8aaaa…", "01k8bbbb…"],
    "leader_server_id": "01k8aaaa…",
    "source_connection_id": "01k8cccc…",
    "repository": "acme/shop",
    "branch": "main",
    "push_to_deploy": true,
    "php_version": "8.4",
    "domain": {"type": "custom", "name": "shop.example.com"},
    "variables": {"DB_URL": "${{ shop-db.DATABASE_URL }}"}
  }'
```

### Docker Compose sites

Set `runtime: "compose"` and:

| Field | Rule |
|---|---|
| `compose_source` | `repo` or `inline` |
| `compose_file` | `repo`: path in the repository; default `compose.yaml`, then `docker-compose.yml` |
| `compose_content` | `inline`: the Compose file (up to 256 KiB, no `build:`; must pass the policy unless privileged compose is allowed) |
| `public_services[]` | Up to 20 `{service, port, domain?, health_check_path?}`; `domain` is a name or a domain choice; `null` / `{"type": "test"}` means the test domain. Generated names are `<service>-<slug>.<ip-with-dashes>.<suffix>`. `health_check_path` is what the deploy health check requests through the service's domain (without it: the site's check path for the first service, any answer below 500 for the others). |
| `compose_files` | `repo`: list of compose files in `-f` order (the first is the project file) |
| `compose_profiles` | `repo`: list of profiles to run |
| `compose_services` | `repo` and `inline`: `{<service>: {mode: keep\|database\|site, engine?, database_id?, site?}}` — run a service as a Falak database or its own site. `engine`: `postgresql\|mysql\|mariadb\|redis\|valkey`; `redis`/`valkey` for services on the official `redis` / `valkey/valkey` images (see [A Falak Redis or Valkey](/docs/guides/compose-apps/#a-falak-redis-or-valkey)) |
| `compose_adjustments` | `repo`: `{keep_binds: ["service:./path"]}` — missing bind sources kept as folders instead of named volumes |

With `compose_files`, creation reads the repository first: the files must load, public services must exist and required `${VAR}`s need a value in `variables` (`422` otherwise).

The response then carries `compose {source, file, version, public_services[] (with host_port, test_domain, url, health_check_path), template}`. After creation, `public_services[].domain` mirrors the service's primary domain; manage a service's domains in **Settings → Networking**. See [Docker Compose](/docs/deploy/docker-compose/) and [Compose apps from git](/docs/guides/compose-apps/).

## `GET /api/v1/sites/{site}/env` — `sites.env.view`

Returns the latest environment version as dotenv. Recorded in the audit log as a reveal. Rate limited to 60/minute.

```json title="200 OK"
{"data": {"content": "APP_ENV=production\nAPP_KEY=base64:…\n", "version": 3}}
```

## `PUT /api/v1/sites/{site}/env` — `sites.env.manage`

Body `{"content": "<dotenv>"}` replaces **all** variables (which keys are exposed to the deploy script is kept). Takes effect on the next deployment.

```json title="200 OK"
{"data": {"version": 4, "changed": true, "keys": ["APP_ENV", "APP_KEY"]}}
```

`422` with `errors.content` when the dotenv cannot be parsed.

## `PUT /api/v1/sites/{site}/laravel` — `sites.manage`

Laravel toggles; each is optional (unchanged when omitted):

| Field | Meaning |
|---|---|
| `scheduler` | Run `schedule:run` every minute on the leader |
| `horizon` | Supervise `php artisan horizon` |
| `octane` | Run Laravel Octane |
| `octane_server` | `frankenphp` (FrankenPHP runtime only; its default), `swoole` (default on PHP-FPM) or `roadrunner` |
| `maintenance` | `artisan down --retry=60` / `artisan up` on every server immediately |

```json title="200 OK"
{"data": {"scheduler": true, "horizon": false, "octane": true, "maintenance": false, "octane_server": "frankenphp", "octane_port": 8412}}
```

The Octane port is allocated by Falak and cannot be set. `422` for a Laravel toggle on a non-Laravel site, an unavailable server, or no free port. See [Laravel Octane](/docs/deploy/laravel-octane/).

Updating other site settings (domains, servers, deploy script, strategy) is not in the public API yet.
