Skip to content

Sites API

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

{"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).

All sites of the organization.

One site.

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.

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.
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 (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}
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.

Terminal window
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 }}"}
}'

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)
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 and Compose apps from git.

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

Section titled “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.

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

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

Section titled “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.

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

Section titled “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
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.