API and MCP
API keys
Long-lived tokens for scripts and AI agents, separate from user accounts — a machine cannot do one-time passwords, and it should not borrow someone's password.
Format is walis_<prefix>_<124-bit secret>; the database keeps only a domain-separated SHA-256 lookup hash and the plaintext is returned once. A fixed-cost hash is the right choice here because the secret is machine-generated and high-entropy, unlike a password.
Keys carry a role (admin, operator, viewer) and a scope:
| Scope | Reaches |
|---|---|
mcp | /mcp only. The safe default for a new key. |
api | REST and MCP, subject to the key's role. |
runner | Register, heartbeat, claim and report — for one runner name only. Set automatically when a key names a runner. |
No key of any scope can touch sign-in, accounts, or key lifecycle, so a leaked key cannot breed more.
curl -H "Authorization: Bearer walis_..." http://127.0.0.1:11120/api/jobsMCP
The API speaks MCP directly at /mcp, so an agent needs a URL and a key — nothing to install:
# Claude Code
claude mcp add --transport http walis http://127.0.0.1:11120/mcp \
--header "Authorization: Bearer walis_..."
# Codex CLI — reads the token from an environment variable
codex mcp add walis --url http://127.0.0.1:11120/mcp --bearer-token-env-var WALIS_API_KEYTools: list_jobs, get_job, trigger_build, wait_for_build, get_build, get_build_log, list_builds. Jobs can be named rather than referenced by id.
Settings, accounts and key management are deliberately not exposed. Those are irreversible or credential-bearing, and belong to a human in the admin UI; a leaked key stays limited to triggering builds and reading logs.
/mcp goes through the same authentication as /api — it can trigger builds, so it is not left open just because it sits outside the /api prefix.
Health
GET /health needs no key and reports which database the node is using, what it calls itself, and whether the deployment is clustered:
{ "status": "ok", "database": "PostgreSQL", "node": "walis-a", "clustered": true }clustered is false on SQLite, which is single-node by definition. Behind a load balancer this is the quickest way to confirm which node answered. See High availability.
Metrics
GET /metrics returns Prometheus text: job counts, queue depth, builds by status, average duration and queue time, runners by state.
It sits outside /api so a local scraper has a stable path that does not move with the API surface, but it is not unauthenticated: the authentication middleware lists /metrics alongside the administrator-only paths. Point your scraper at it with an admin-scoped key.
Portability
GET /api/portability/export exports every job and custom action without ids, statistics or timestamps — those describe this machine, not the definition. Import matches by name: existing jobs are skipped unless you ask to overwrite, and overwriting updates them in place so build history survives.