# Servers API

> List, show, create and delete Falak servers through the REST API, get the install command for custom servers, and upgrade a server's agent.

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

## The server resource

```json
{
  "id": "01k…",
  "name": "app-1",
  "type": "app",
  "type_label": "App server",
  "status": "active",
  "status_message": null,
  "provider": "custom",
  "provider_label": "Custom (bring your own server)",
  "region": null,
  "ipv4": "203.0.113.20",
  "private_ipv4": "10.0.0.5",
  "ssh_port": 22,
  "php": "8.4",
  "agent": {
    "status": "online",
    "last_heartbeat_at": "2026-09-28T10:00:00+00:00",
    "version": "v0.2.6",
    "available_version": "v0.2.6",
    "update_available": false,
    "upgrade": null
  },
  "load1": 0.12,
  "cpu_percent": 3.4,
  "memory_percent": 41.2,
  "disk_percent": 22.9,
  "created_at": "2026-09-26T10:00:00+00:00"
}
```

| Field | Values |
|---|---|
| `type` | `app`, `web`, `db`, `cache`, `worker`, `lb`, `builder` |
| `status` | `creating`, `provisioning` (machine check and plan), `needs_attention` (the machine check found a conflict; nothing was applied; `status_message` lists it), `active`, `error`, `deleting`. Only `active` servers are deploy targets. |
| `provider` | `hetzner`, `digitalocean`, `vultr`, `linode`, `aws`, `custom` |
| `private_ipv4` | The server's address on its private network, or `null` when it has none (`falak ssh --private` uses it) |
| `ssh_port` | The server's SSH port |
| `php` | The default PHP version, or `null` |
| `agent` | `null` until an agent enrolled |
| `agent.status` | `online` / `offline` (and other Fleet states) |
| `agent.upgrade` | The latest upgrade: `status` (`queued`, `running`, `succeeded`, `failed`, `cancelled`), `from_version`, `to_version`, `error`, `requested_at`, `finished_at` |

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

All servers of the organization, ordered by name, in `{"data": [...]}`.

## `GET /api/v1/servers/{server}` — `servers.view`

One server, plus `stack` (the chosen software) and `php_versions` (installed PHP versions).

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

| Field | Rule |
|---|---|
| `name` | Required; up to 64 characters, `^[A-Za-z0-9][A-Za-z0-9 ._-]*$`, unique in the organization |
| `type` | Required; a server type |
| `provider` | Required; a provider value |
| `credential_id` | Provider credential (not for `custom`) |
| `region`, `size`, `image` | Provider catalog values |
| `timezone` | IANA timezone |
| `stack.php` | `{runtime, versions[], default}`; `runtime` is `frankenphp` or `fpm` |
| `stack.node` | `"20"`, `"22"` or `"24"` |
| `stack.database` | `postgresql`, `mysql` or `mariadb` |
| `stack.cache` | `redis` or `valkey` |
| `stack.docker` | Boolean |
| `ssh_key_ids[]` | Organization SSH keys to install |

```bash
curl -X POST https://falak.example.com/api/v1/servers \
  -H "Authorization: Bearer $FALAK_TOKEN" -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"name": "app-1", "type": "app", "provider": "custom",
       "stack": {"php": {"runtime": "frankenphp", "versions": ["8.4"], "default": "8.4"},
                 "node": "22", "database": "postgresql", "cache": "redis", "docker": false}}'
```

`201` with the server resource plus `install_command` (custom servers only; `null` otherwise):

```json
{"data": {"id": "01k…", "name": "app-1", "status": "creating", "…": "…",
          "install_command": "curl -fsSL https://falak.example.com/install/Xy3…a9 | sudo sh"}}
```

Run `install_command` as root on the machine. See [Connect your own server](/docs/servers/connect-custom-server/).

## Machine check

Servers whose agent is v0.6.0 or later are checked before provisioning (see [Machine check](/docs/servers/machine-check/)). Each component (`base`, `docker`, `database`, `cache`, `edge`, `php`, `node`, `ssh`, `firewall`, `swap`, `hostname`, `unattended_upgrades`, `fail2ban`) gets a `decision`: `install`, `adopt`, `complete`, `block` or `skip`.

### `GET /api/v1/servers/{server}/inspection` — `servers.view`

The latest check and the decisions for the server's current stack; `404` when there is none (older agents, not enrolled yet).

```json
{"data": {"supported": true, "status": "finished", "purpose": "provision", "checked_at": "2026-10-13T09:12:00+00:00",
  "agent_version": "v0.6.0", "blocking": true,
  "summary": "Machine check: 1 conflict to fix before provisioning. Port 80 is in use by nginx, which Falak's edge needs.",
  "components": [{"component": "edge", "decision": "block", "severity": "block",
    "reason": "Port 80 is in use by nginx, which Falak's edge needs.",
    "hint": "Stop and disable it (systemctl disable --now nginx.service) or move it to another port, then re-check.",
    "found": [{"name": "nginx", "version": "1.24.0", "source": "Ubuntu archive"}], "install": [], "keep": []}]}}
```

`status` is `running`, `finished` or `failed`. Components sort blocks first, then warnings.

### `POST /api/v1/servers/{server}/inspection` — `servers.manage`

Re-check: runs the check again and never applies anything. `202`; `422` when the agent is too old or offline, or a check is already running. 10/min.

### `POST /api/v1/servers/{server}/provision` — `servers.manage`

The **Provision** button: applies the plan from the latest check, for a server in `needs_attention` or `error` whose latest check finished without blocks. `202` `{"data": {"status": "provisioning", "command_id": "01k…"}}`; `422` with the summary of what blocks. 10/min.

### `POST /api/v1/servers/{server}/reprovision` — `servers.manage`

**Re-provision**: the machine check first, then the plan. A server that was provisioned before keeps its status; when something blocks, its `status_message` starts with "Re-provisioning stopped." and nothing is applied. `202`. 10/min.

## `DELETE /api/v1/servers/{server}` — `servers.delete`

Deletes the server. For provider servers, the machine is destroyed at the provider unless you pass `destroy_at_provider=false`. Returns `202` with no body.

```bash
curl -X DELETE "https://falak.example.com/api/v1/servers/01k…?destroy_at_provider=false" \
  -H "Authorization: Bearer $FALAK_TOKEN" -H "Accept: application/json"
```

## `POST /api/v1/servers/{server}/database-engine` — `servers.manage`

Installs a database or cache engine on a provisioned server that has none. Rate limited to 10/minute.

```json title="Request body"
{"engine": "postgresql"}
```

`engine` is `postgresql`, `mysql` or `mariadb` for a SQL database engine, or `redis`/`valkey` for a cache engine on a server type with a cache component (`app`, `cache`) — Valkey only where the OS packages it (Ubuntu 24.04 and 26.04, Debian 13; see [Redis and Valkey](/docs/databases/redis-and-valkey/#versions-per-os)). One engine install runs at a time. The engine joins the server's stack and the provisioning plan converges with it (the distribution's packages and service, as at creation).

```json title="202 Accepted"
{"data": {"engine": "postgresql", "status": "installing", "command_id": "01k…"}}
```

Once the agent reports success, the engine appears under **Databases** (and, on app servers, is reachable from the server's containers). When the plan fails, the engine is taken back out of the stack (audit event `server.database_engine_install_failed`).

| Status | Meaning |
|---|---|
| `202` | Install started |
| `422` | Unsupported engine; the server already runs (or is installing) one of that kind; a server type without that kind (only `app` servers can add a SQL or cache engine later; `db` and `cache` servers always have one); the server is not active; or the machine check blocks it (another engine of the kind, or its port taken — the message names it) |

## `POST /api/v1/servers/{server}/agent/upgrade` — `fleet.agents.manage`

Upgrades the server's agent to the build this control plane ships. Rate limited to 30/minute.

```json title="202 Accepted"
{"data": {"id": "01k…", "server_id": "01k…", "status": "running", "from_version": "v0.3.0", "to_version": "v0.4.0",
          "rollout_id": null, "error": null, "requested_at": "2026-09-28T10:00:00+00:00", "finished_at": null}}
```

If an upgrade is already running, the same upgrade is returned. It succeeds once the restarted agent reports the new build, and fails on a download, checksum or pre-flight error, or after `FALAK_AGENT_UPGRADE_TIMEOUT` seconds.

| Status | Meaning |
|---|---|
| `202` | Upgrade started (or already running) |
| `409` | No agent, agent offline, no verifiable build for the server's architecture, or already on that build |

The server resource does not include the SSH user yet.
