Admin API

August 7, 2026 ยท View on GitHub

The Admin API is a Flask JSON API over the same core objects used by the CLI. Handlers should validate input, enforce auth, and delegate work.

Layout

admin/backend/api/v1/
  benches/   bench creation, readiness, support data
  setup/     first-run and database setup
  settings/  bench config read/write/apply
  sites/     site apps, backups, domains, login, config
  apps.py    bench app inventory and actions
  tasks.py   task list, logs, events, control
  logs.py    log access
  processes.py
  stats.py
  updates.py
  ssh_keys.py
  databases.py
  git.py

Backend provider integrations live under admin/backend/providers.

Handler Rules

  • Resolve Bench, Site, Server, or App early.
  • Put business behavior on core objects or task classes.
  • Return task ids for long work.
  • Keep route helpers public when another route imports them.
  • Do not import private functions across route modules.

Auth

Admin auth code lives under the admin backend, not in route files. Routes should depend on the shared auth helpers and avoid hand-parsing credentials.

Supported auth modes include local Admin sessions and trusted remote JWKS tokens when configured in [admin].

Only these routes answer without a session: GET /health, GET /bootstrap, and the three /auth/session methods. GET /bootstrap returns just mode, enabled, and name until the caller has one. Add @allow_unauthenticated only with the same kind of reason.

A ?sid=<token> link is exchanged for a session cookie by POST /auth/session. Setup links (Session.issue_setup_link_token) live one hour and mint a 3-hour session; a password login mints the full 24 hours.

Response Shape

Prefer small response models that match UI needs. Include stable ids, names, status, and task ids. Avoid returning raw config objects when only a few fields are needed.

Task-starting endpoints should return:

{
  "task_id": "task-id",
  "created": true
}

created is useful for idempotent submissions.

Site Apps

GET /sites/<name>/apps returns the apps in use on the site, disabled ones excluded, plus can_disable for whether this bench's Frappe supports disabling at all.

Two app operations answer inline instead of returning a task id, because both are flag flips on data that never left the site:

  • DELETE /sites/<name>/apps/<app>?mode=disable returns {"app": ..., "disabled": true}. Without the parameter the route queues an uninstall as before.
  • POST /sites/<name>/apps for an app the site only has disabled returns {"app": ..., "enabled": true}. It falls through to the install queue when a required app has to be installed first.

Setup

Every /setup/* route needs a session, like the rest of the API. The Admin password is set when the bench is created (pilot new), so there is no unauthenticated window: a browser reaches the wizard through the ?sid= link that pilot start prints, or by signing in with that password. POST /benches returns a setup_link token for the same purpose.

PUT /setup/configuration accepts only the fields the wizard owns: app_repo, app_branch, db_type, db_mode, and the mariadb_*/postgres_* connection fields. Any other key gets a 422 - including admin_password. Change the remaining bench.toml settings through the settings API.

Errors

Raise HTTP errors at the route boundary. Core objects should raise domain exceptions such as config or bench errors.

Routes should translate known domain errors into clear HTTP status codes and messages. Unexpected errors should remain visible in logs.

Events And Logs

Task event and log endpoints expose task runner state. The Admin UI depends on step events, final status, and streaming logs for long operations.

Do not parse task output in route handlers except through the task runner APIs.

Adding Endpoints

  1. Place the route in the closest group. 2. Add a request/response model if the shape is not trivial. 3. Resolve the domain object and delegate. 4. Queue a task for long work. 5. Add backend tests for success and error behavior.