REST API Reference

The HTTP API exposed by the Saba-chan daemon. Authentication scheme and a catalog of the major endpoints.

REST API Reference

The Saba-chan daemon (saba-core) exposes an HTTP REST API on 127.0.0.1:57474. The GUI and CLI call only this API, and Discord commands arriving through the Cloud Relay are processed by the same functionality inside the daemon. You can build a custom client against the same contract.

This reference summarizes the routes a user or integrator is likely to need, taken from the daemon's route definitions (src/ipc/). Some internal routes are omitted, and field schemas may change, so consult the code for the latest.


1. Basics

  • Base URL: http://127.0.0.1:57474 (default). Can be changed via the SABA_IPC_PORT environment variable or gui.ipcPort in config.json (takes effect after the daemon restarts).
  • Bind address: Fixed to 127.0.0.1, so it isn't reachable from outside. Requests whose Host header is not loopback (127.0.0.1, localhost, ::1) get a 403.
  • Authentication: Every request except /health needs an X-Saba-Token header. The daemon generates a new token each time it starts and writes it to the .ipc_token file in the data folder (Windows: %APPDATA%\saba-chan\.ipc_token). A missing or wrong token gets a 401 with no body.
  • Content-Type: Request bodies are application/json. Most responses are JSON too (extension GUI bundles, icons, etc. are exceptions).
  • Error format: Varies by route. Common shapes are {"error": "...", "error_code": "..."}, {"success": false, "error": ..., "error_code": ...}, and {"ok": false, "error": ...}. Some routes return HTTP 200 with ok:false on failure, so check the body as well.

2. Health check & daemon

GET  /health                     # no token required
GET  /api/version
GET  /api/diagnostics
GET  /api/events                 # event stream (SSE)
POST /api/daemon/shutdown
  • /health: {"ok": true, "version": "x.y.z", "pid": 1234, "token": "valid" | "invalid" | "missing"}. token only tells you whether the X-Saba-Token you sent is correct; it never reveals the value.
  • /api/version: {version, components: {...}} (core, module, and extension versions).
  • /api/events: Server-Sent Events. The main kinds are instance.exit (server exit / crash verdict), backup.done, update.available, schedule.warn, provision.progress, and action.run. When reconnecting, you can catch up on missed events with the Last-Event-ID header or ?since=<n>.
  • /api/daemon/shutdown: Shuts the daemon down.

3. Servers (instances)

Saba-chan manages each server as an instance, identified by a UUID (id). The older /api/server/:name/* paths identify servers by name, but new clients should use /api/instance/:id/*.

3.1 Listing, creating, updating, deleting

GET    /api/servers                        # server list + running state
GET    /api/instances                      # instance list
POST   /api/instances                      # create
PUT    /api/instances/reorder              # body: { "order": [id, ...] }
GET    /api/instance/:id                   # details (+ terms consent state in agreements)
PATCH  /api/instance/:id                   # change settings (key-value object)
DELETE /api/instance/:id                   # delete
POST   /api/instance/:id/rename            # body: { "name": "..." }
  • Main fields of POST /api/instances: { "name", "module_name", "port"?, "preset"?, "module_settings"?, "accept_agreements"?: [id] }. On success it returns 201 {success, id, ...}, and if the module defines an install step, installing the server files starts in the background (§3.5).
  • Besides the port and module settings, PATCH /api/instance/:id also accepts daemon settings such as:
    • auto_restart, restart_on_unknown_exit: whether to restart automatically after a crash / an unknown exit
    • extra_args: extra launch arguments (a string or an array of strings; null clears them)
    • memory_limit_mb: memory limit (MiB, at least 256; "4G"/"4096M" forms are accepted; null/0 means no limit). Applies on Windows only.
  • Renaming requires the server to be stopped; a name already in use returns 409 name_taken.

3.2 Start / stop / restart

POST /api/instance/:id/start
POST /api/instance/:id/stop                # optional body: { "force": true }
POST /api/instance/:id/restart             # optional body: { "force": true }
GET  /api/instance/:id/events              # recent exit history
GET  /api/server/:name/status              # status by name (older path)
  • Before starting, the daemon checks terms consent and runs the module's checks. Unaccepted terms return 409 agreement_required (with agreements); a critical check failure returns 409 preflight_failed.
  • Stopping sends the game's graceful stop command, waits for the module's timeout (30 seconds by default), and force-kills the process if it's still alive. With force: true it is killed immediately.
  • events returns {events: [...]} with the exit verdict (user_stop, in_game_stop, crash, unknown_exit), its evidence, any scheduled auto restart, and so on.

3.3 Console and commands

GET  /api/instance/:id/console?since=<id>&count=<n>
POST /api/instance/:id/stdin               # body: { "command": "say hello" }
POST /api/instance/:id/command             # body: { "command": "<name>", "args"?: {...} }
GET  /api/instance/:id/commands            # commands available for this server
POST /api/instance/:id/rest                # REST-only games (e.g. Palworld)
POST /api/instance/:id/rcon                # compatibility name; actually sends to the console (stdin)
  • Console polling: The response is {success, lines: [{id, timestamp, source, content, level}], running, ...}. With since, lines whose id is greater than or equal to it are returned, so send the last id you received + 1 next time. Without since, you get the most recent count lines (default 100). The daemon keeps the last 10,000 lines per server in memory.
  • RCON is not used. Commands go to the server console (stdin), and games that support only REST, like Palworld, go through REST. /rcon is a path kept only by name for older clients and always sends to stdin. Servers without a console return 409 no_command_path.
  • /stdin returns 409 stdin_not_supported if the module doesn't support stdin.
  • /command runs a command defined by the module; if the server is stopped it returns 409 server_not_running.

3.4 Settings, terms, checks

POST /api/instance/:id/launch-preview      # body: { "settings"?: {...}, "extra_args"?: "..." }
POST /api/instance/:id/agreements          # body: { "accept": ["eula"] }
GET  /api/instance/:id/properties          # read the game's config file
PUT  /api/instance/:id/properties          # body: { "settings": {...} }
POST /api/instance/:id/validate            # server check
POST /api/instance/:id/diagnose            # log diagnosis
  • launch-preview computes the launch command line without starting the server. Example response: {success: true, has_launch_args, command_line, program, args, extra_args, errors: [...]}. Password values are masked as ***. If the module declares no launch arguments, it returns has_launch_args: false.
  • agreements records consent to terms the module declares (e.g. Minecraft eula). It works locally only; requests from remote sources (Discord, etc.) get 403 agreement_local_only. Undeclared ids get 400 invalid_format.

3.5 Install progress and game server updates

GET    /api/provision-progress/:name       # :name = instance name
DELETE /api/provision-progress/:name       # dismiss a failed progress state
GET    /api/instance/:id/check-update
POST   /api/instance/:id/apply-update

Example provision-progress response:

{
  "active": true,
  "step": 2,
  "total": 5,
  "label": "...",
  "message": "...",
  "percent": 40,
  "done": false,
  "error": null
}

When nothing is in progress it returns {"active": false}. A successful progress state is cleared automatically after 5 seconds; a failed one stays until you dismiss it with DELETE (if the server was being newly created, it is rolled back at that point). Progress is also available as provision.progress on /api/events.

apply-update returns 409 server_running if the server is running. A backup is taken before updating (for modules that support backups).

3.6 Backups and scheduled tasks

GET    /api/instance/:id/backups                     # { success, backups: [{ name, kind, created, size }] }
POST   /api/instance/:id/backups                     # back up now
DELETE /api/instance/:id/backups/:name
POST   /api/instance/:id/backups/:name/restore       # server must be stopped
GET    /api/instance/:id/schedule
PUT    /api/instance/:id/schedule                    # body: { "schedule": [...], "backup_keep"?: 10 }
  • Backup kind: manual, scheduled, pre-update, pre-restore. If the module doesn't support backups you get 400 backup_unsupported; if there's nothing to back up yet, 400 nothing_to_back_up.
  • Restoring returns 409 server_running if the server is running, and the current state is backed up as pre-restore before restoring.
  • The shape of one scheduled task:
{ "action": "restart", "at": "05:00", "warn_minutes": [10, 5, 1], "enabled": true }

action is backup | restart | announce | update, and you use exactly one of every_minutes (every N minutes) and at (daily at HH:MM). announce needs a message, and warn_minutes applies only to restart and update. Invalid tasks return 400 validation_failed (with details).


4. Modules

GET    /api/modules                        # installed modules
POST   /api/modules/refresh                # re-read the modules folder
GET    /api/module/:name                   # module metadata
GET    /api/module/:name/versions          # installable game server versions
GET    /api/module/:name/locales
GET    /api/modules/manifest               # catalog of installable modules
POST   /api/modules/manifest/:id/install   # install a module from the catalog
DELETE /api/modules/:id                    # remove a module

The module catalog comes from the signed release list, and downloaded files are installed only after their hash is verified.


5. Extensions

GET    /api/extensions                     # list with install/enabled state
POST   /api/extensions/rescan
GET    /api/extensions/manifest            # catalog of installable extensions
GET    /api/extensions/updates
POST   /api/extensions/:id/install
DELETE /api/extensions/:id
POST   /api/extensions/:id/enable          # runs the daemon.startup hook asynchronously
POST   /api/extensions/:id/disable

GET    /api/extensions/:id/config
PUT    /api/extensions/:id/config

GET    /api/extensions/:id/gui             # GUI bundle (UMD JavaScript)
GET    /api/extensions/:id/gui/styles      # CSS
GET    /api/extensions/:id/i18n/:locale    # ko / en / ja / ...

Errors from extension routes use the {success: false, error, error_code, ...} shape.


6. Settings

GET  /api/config/gui
PUT  /api/config/gui                       # partial JSON (shallow merge)
GET  /api/config/bot
PUT  /api/config/bot                       # bot settings such as prefix and aliases (partial JSON)
GET  /api/system/metrics                   # CPU, memory, disk, per-server usage

All user settings are stored per namespace in a single config.json file in the data folder. The daemon syncs bot settings to the relay. The Discord bot itself runs on the Cloud Relay, so the daemon has no API to start or stop a bot process.


7. Updates

GET  /api/updates/status                   # current state and per-component versions
POST /api/updates/check                    # check for updates now
POST /api/updates/download                 # optional body: { "components": [...] }
GET  /api/updates/download/progress
POST /api/updates/apply                    # optional body: { "components": [...] }
POST /api/updates/launch-updater           # launch the updater (the daemon stops)
GET  /api/updates/config
PUT  /api/updates/config

Update information comes from the signed release list. Components whose running files must be replaced are flagged in the apply response as needing the updater; calling launch-updater starts the separate updater, which stops, replaces, and restarts the daemon. These routes respond in the {ok: ...} shape.


8. Cloud Relay pairing

For the GUI ↔ relay server pairing flow. See the Discord Bot page for the detailed flow.

POST   /api/relay/pair/initiate            # issue a pairing code (forwarded to the relay)
GET    /api/relay/pair/:code/status        # poll whether pairing has completed
GET    /api/relay/agent                    # relay agent status
POST   /api/relay/agent/start
POST   /api/relay/agent/stop
DELETE /api/config/node-token              # delete this PC's node token (stops the agent)

Once pairing completes (status: "claimed"), the daemon saves the node token automatically and reconnects the relay agent with the new token. The daemon handles authentication toward the relay, so clients don't need to deal with it.


9. Authentication example (curl)

TOKEN=$(cat "$APPDATA/saba-chan/.ipc_token")

curl -H "X-Saba-Token: $TOKEN" \
     http://127.0.0.1:57474/api/servers

curl -X POST \
     -H "X-Saba-Token: $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"command":"list"}' \
     http://127.0.0.1:57474/api/instance/<instance-id>/stdin

<instance-id> is the id from the GET /api/servers or GET /api/instances response.


10. Version compatibility

  • This API is classified as a private IPC. It isn't a public contract, so paths and schemas may change even in minor versions.
  • If you distribute a custom client, check the daemon version (version from GET /health) and handle it conservatively.

If you run into problems, head to Troubleshooting.