Reference

Admin API

scalwsd serves one admin API, HTTP/1.1 with JSON or plain-text answers, on up to four transports. scalwsctl and the console use it; this page lists every endpoint for tools that call it directly. Design records: ADR-0017 (socket and views), ADR-0023 (remote admin) and ADR-0049 (upstream pools).

Transports

TransportConfigured byAccessServes
Admin socketserver.admin.socket, default /run/scalws/admin.sockUnix socket, mode 0660: the server’s user and group. Each change is audited with the peer’s uid, gid and pid.Every endpoint.
Remote adminserver.admin.remoteTCP with mutual TLS: only clients with a certificate from client_ca. Each change is audited with the certificate subject and the client address.Every endpoint, like the socket.
Metrics listenerserver.metrics.addressLoopback TCP, no authentication.GET of the operational views marked “metrics” below. Changes answer 403; other views answer 403 too.
Probe listenerserver.health.addressTCP, no authentication.GET /healthz, GET /readyz, and GET /metrics with server.health.metrics: true; everything else is 404.

Paths may carry a /v1 prefix (/v1/status and /status are the same endpoint); tools should use it. Changes use POST (or PUT for upstream members); other methods answer 405, unknown paths 404. Error answers are one line of plain text.

Over the socket with curl:

curl --unix-socket /run/scalws/admin.sock http://localhost/v1/status

Views

All views are GET. “Metrics” marks the views the metrics listener serves as well.

PathMetricsAnswer
/v1/metricsyesPrometheus text format, with the per-domain counters. See logging-metrics.md.
/v1/healthzyes200 ok, or 503 draining while the process shuts down.
/v1/readyzyes{"state": "ready" | "active" | "draining", "generation", "instance", "pid"}; 503 while draining.
/v1/statusyesVersion, generation, uptime, listeners, metrics address, admin socket, counts of tenants and applications, enabled subsystems, draining.
/v1/appsyesOne entry per application: domains, runtime type, state, workers, requests in flight, recent requests and p95, diagnostic findings.
/v1/diagnose?app=tenant/appyesDiagnostics of every application, or of one; 404 for an unknown application or before the first window.
/v1/optimizeryesThe optimizer journal.
/v1/explain?app=tenant/appyesThe last advice of the local advisor; 400 without app, 404 without advice, 503 when server.ai is off.
/v1/dashboard, /yesThe read-only HTML dashboard.
/v1/tenantsnoTenants with their user, applications, resources and, with cgroups, memory and CPU usage.
/v1/tenants/usage?interval_ms=1000noCPU, memory, I/O and pressure of each tenant over the interval (200 to 10,000 ms), with its limits; 409 without cgroup v2.
/v1/config/historynoThe kept configuration generations with time and source.
/v1/config/diff?from=N&to=MnoLine diff of two kept generations (plain text); 404 for a generation that is not kept.
/v1/certsnoACME certificate status per application.
/v1/upstreamsnoEvery upstream pool with its members (below).
/v1/upstreams/{name}noOne pool; 404 unknown pool.

Changes

Available on the admin socket and the remote admin listener only.

Method and pathEffectAnswers
POST /v1/reloadReads the configuration file and applies it.200 {"generation": N}; 422 with the reason when it is refused.
POST /v1/rollback?generation=NApplies a kept generation again (default: the previous one).200 {"generation": N}; 400 for a non-numeric generation; 409 with the reason.
POST /v1/apps/{tenant}/{app}/restartStarts a fresh instance of the application’s runtime, then drains the old one.200 {"generation": N}; 409 with the reason.
POST /v1/apps/{tenant}/{app}/scale?workers=NSets the worker target of a managed Node.js pool within its bounds.200 {"from", "to", "min", "max"}; 400 without workers; 404 unknown application; 409 for a runtime that cannot be scaled.
POST /v1/cache/purge?host=H&path=P (or host and prefix, or tag, or all=1)Removes cached responses.200 purged; 400 for another combination; 404 cache disabled.
POST /v1/explain?app=tenant/appQueues a request to the local advisor.202 queued; 400 without app; 404 unknown application; 429 when the queue is full; 503 when server.ai is off.
POST /v1/tenants/{tenant}/limits?cpu=&memory=&memory_percent=&io_weight=&io_bandwidth=&clear=Sets (or with clear=cpu,memory,io_weight,io_bandwidth or all removes) run-time cgroup limits of a tenant, kept until scalwsd restarts.200 with the override and the effective limits; 400 invalid value; 404 unknown tenant; 409 without cgroup v2; 500 when the kernel refused it.
PUT /v1/upstreams/{name}Replaces the members of a members_from: api pool (below).200, 400, 404, 409, 413, 501.

Upstream pools

GET /v1/upstreams

Every pool of the active configuration, sorted by name:

{
  "pools": [
    {
      "name": "api",
      "members_from": "api",
      "balance": "round_robin",
      "members": [
        {
          "address": "192.0.2.5:8080",
          "weight": 2,
          "active_requests": 1,
          "consecutive_failures": 0,
          "ejected_until_unix_ms": null
        },
        {
          "address": "192.0.2.6:8080",
          "weight": 1,
          "active_requests": 0,
          "consecutive_failures": 0,
          "ejected_until_unix_ms": 1792000000000
        }
      ]
    }
  ]
}
FieldMeaning
namePool name (upstreams.<name>).
members_fromfile or api.
balanceround_robin, least_conn or hash.
members[].addressCanonical member address: host:port, [ipv6]:port or unix:/path.
members[].weightWeight, 1 to 1000.
members[].active_requestsRequests in flight on the member (until their response body is finished).
members[].consecutive_failuresFailures since the last success or ejection.
members[].ejected_until_unix_msEnd of the current ejection in Unix milliseconds; null while the member takes traffic.

With no pools the answer is {"pools": []}.

GET /v1/upstreams/{name}

One pool, as one element of pools above. 404 unknown pool for a name that is not configured.

PUT /v1/upstreams/{name}

Replaces the whole member list of a pool with members_from: api, without a reload:

curl --unix-socket /run/scalws/admin.sock -X PUT \
  --data '{"members": [{"address": "192.0.2.5:8080", "weight": 2}, {"address": "192.0.2.6:8080"}]}' \
  http://localhost/v1/upstreams/api

The body is a JSON object with exactly one key, members: a list of {"address": "...", "weight": N}, where weight is optional (default 1). Addresses follow the configuration’s rules: ip:port, [ipv6]:port, host:port, or unix:/path as the only member of a plain-HTTP pool. An empty list empties the pool (its requests then get 503 with Retry-After: 1).

StatusBodyWhen
200The pool, as in GET /v1/upstreams/{name}The list was installed.
400invalid body: ... or the first problem, for example members[1].weight: must be between 1 and 1000Not a valid body; an invalid address or weight; a duplicate member; more than 10,000 members.
404unknown poolNo pool of that name.
409the pool takes its members from the configuration file (members_from: file)A file pool.
413body larger than 2 MiBThe body is too large.
501run-time pool members are not supported under scalws-controller yetThe process is an instance of scalws-controller (listener_mode: inherited or reuseport).
403Sent to the metrics listener.

A refused request changes nothing. The change is serialised with reloads; members that stay (same canonical address) keep their connections, load and health, requests in flight to a removed member finish, and the series of removed members leave the metrics. Members set this way survive reloads but not a restart. See load-balancing.md.

scalwsctl upstreams, scalwsctl upstreams show <pool> and scalwsctl upstreams set <pool> <addr[=weight]>... call these endpoints (scalwsctl).

Under scalws-controller

The controller’s local control API (controller.socket, default /run/scalws-controller/control.sock, mode 0600; see adaptive-runtime.md) answers the same three calls without the /v1 prefix:

Method and pathAnswer
GET /upstreams, GET /upstreams/{name}Forwarded to the admin socket of one serving instance: 200 {"ok": true, "result": <the instance's answer>}, or 502 {"ok": false, ...} with the instance’s status and answer, the connection error, or no serving instance.
PUT /upstreams/{name}501 {"error": "run-time pool members are not supported under scalws-controller yet; use members_from: file"}.

A pool name that does not follow the name rule (lower-case letter first, then a-z, 0-9, -, _, at most 63 characters) is 404 there.