API

System API

Self-hosted instance administration — onboarding, SSH servers, component install, tunnels, filesystem browse, team-mode migration, and whole-instance data transfer.

The System API runs the machinery behind a self-hosted Openship box: first-run onboarding, the SSH servers you deploy onto, installing the components those servers need (Docker, OpenResty, certbot…), port-forward tunnels, browsing the host filesystem, migrating a single-user instance into team mode, and exporting or importing the entire instance. In the dashboard this is spread across the onboarding wizard and the Settings → Servers area; from the terminal it's openship server and openship system.

Self-hosted only

This module is mounted only when the API is not running in cloud mode — in cloud mode the routes and their controllers are never loaded (node:fs, admin-user creation, and SSH management don't exist in the cloud runtime). Every path below is unavailable on Openship Cloud.

Base path & auth

All paths are relative to your instance, under /api — e.g. https://your-host/api/system/servers. Send a personal access token as a bearer header (Authorization: Bearer <token>), created with openship token create. The dashboard uses your session cookie instead. See the API overview and auth model for details.

Three route groups are exceptions: /onboarding and /upgrade-to-auth are public (they run before any user or session exists), and the two /setup routes are public to the user session but guarded by internalAuth — a shared token used only by the desktop app pushing config to its embedded API.

Endpoints

Method & pathPermissionWhat it does
GET /api/system/onboardingpublicFirst-run check — is any server configured yet?
POST /api/system/onboardingpublicFirst-run setup; persists settings and creates the first server. Returns 403 once configured.
POST /api/system/setuppublic · internalAuthPush instance config from the desktop app.
GET /api/system/setuppublic · internalAuthRead instance config for the desktop app.
POST /api/system/upgrade-to-authpublicPromote a zero-auth instance to email/password login.
GET /api/system/settingssettings:readRead instance settings.
PATCH /api/system/settingssettings:writeUpdate instance-level (non-SSH) settings.
DELETE /api/system/settingssettings:adminReset instance settings and remove all servers.
GET /api/system/serversserver:listList servers in the active organization.
GET /api/system/servers/:idserver:readGet one server (SSH secrets omitted).
POST /api/system/serversserver:writeAdd an SSH server.
PATCH /api/system/servers/:idserver:writeUpdate an SSH server.
DELETE /api/system/servers/:idserver:adminDelete an SSH server.
GET /api/system/servers/:id/rate-limitserver:readRead the server's OpenResty rate-limit config.
PATCH /api/system/servers/:id/rate-limitserver:writeUpdate the OpenResty rate-limit config (handler requires admin).
GET /api/system/servers/:id/tunnelsserver:readList port-forward tunnels and live status (desktop only).
POST /api/system/servers/:id/tunnelsserver:writeSave a port-forward config (desktop only).
POST /api/system/servers/:id/tunnels/:tunnelId/startserver:writeOpen a tunnel.
POST /api/system/servers/:id/tunnels/:tunnelId/stopserver:writeClose a tunnel.
DELETE /api/system/servers/:id/tunnels/:tunnelIdserver:writeDelete a tunnel config.
POST /api/system/test-connectionserver:writeTest SSH credentials without saving them.
POST /api/system/checkserver:writeRun component health checks against a saved server.
POST /api/system/installserver:adminInstall one component on a server.
POST /api/system/removeserver:adminRemove one component from a server.
POST /api/system/install/streamserver:adminInstall multiple components with live SSE logs.
GET /api/system/install/streamserver:readRe-attach to a running install session's SSE stream.
GET /api/system/install/sessionserver:readGet the active (or a specific) install session's state.
GET /api/system/monitor/streamserver:readStream live CPU / memory / disk stats over SSE.
GET /api/system/browsesettings:readList child directories of a path (folder picker).
POST /api/system/migration/preflightsettings:adminTeam-migration readiness check (read-only).
POST /api/system/migration/startsettings:adminMigrate single-user → your own remote server.
POST /api/system/migration/start-cloudsettings:adminMigrate → Openship Cloud.
POST /api/system/migration/start-tunnelsettings:adminExpose this instance via an edge tunnel.
POST /api/system/migration/switch-backsettings:adminReverse a migration back to single-user.
POST /api/system/data-transfer/exportsettings:adminExport the whole instance (owner only).
POST /api/system/data-transfer/importsettings:adminImport a whole-instance file, ≤500 MB (owner only).

Server routes are org-scoped and IDOR-safe

A :id that isn't a server in your active organization returns 404, indistinguishable from a missing one. The server:* permission is resolved per-server on top of the route tag.

Onboarding & setup

GET /api/system/onboarding returns { configured: boolean }true once at least one server exists. The POST variant runs the same logic as the internal /setup push but only while the instance is unconfigured (it returns 403 "Instance already configured" afterward), so first-run flows don't need a token.

POST /api/system/onboarding

Prop

Type

openship system onboarding apply --ssh-host 203.0.113.10 --ssh-user root --ssh-auth-method agent

The POST /api/system/setup route accepts the same body but is reserved for the desktop app and requires the internalAuth shared token rather than a user session; it can also target an existing server by passing serverId.

Instance settings

GET /api/system/settings returns the instance config (auth mode, tunnel provider, default build mode and rollback window, invitation mail source, team mode, migration target). PATCH updates only the fields you send. DELETE wipes instance settings and every server row.

PATCH /api/system/settings

Prop

Type

curl -X PATCH https://your-host/api/system/settings \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"defaultBuildMode":"docker"}'
# Same thing from the CLI:
openship system settings set --default-build-mode docker

Zero-auth is double-gated

Setting authMode: "none" on anything other than a desktop deployment means anyone who can reach the API acts as admin. The operator must both start the process with OPENSHIP_ALLOW_ZERO_AUTH=true (else 403) and send confirm: "I-understand-no-auth" in the body (else 400).

Promote zero-auth to a real login

POST /api/system/upgrade-to-auth turns the synthetic zero-auth user into an email/password account and flips authMode from none to local, keeping the same user id so existing projects, deployments, and audit rows still resolve. It is only callable while authMode === "none" (otherwise 400).

POST /api/system/upgrade-to-auth

Prop

Type

openship system upgrade-to-auth --name "Ada" --email ada@example.com

Servers

A server is an SSH target you deploy onto. Responses never include SSH secrets — passwords and key passphrases are encrypted at rest and only decrypted inside the SSH client. See the custom servers guide.

POST /api/system/servers

Prop

Type

PATCH /api/system/servers/:id takes the same fields, all optional — only the keys you send are changed.

curl -X POST https://your-host/api/system/servers \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sshHost":"203.0.113.10","sshUser":"root","sshAuthMethod":"agent"}'
# Same thing from the CLI:
openship server add --host 203.0.113.10 --user root --auth-method agent

Server health check & install

Test a connection before saving, check what a saved server has installed, then install or remove components. These routes identify the server by serverId in the body (not a URL param), and each handler runs its own per-server server:admin (or server:read) check.

POST /api/system/test-connection opens an ephemeral SSH connection from body credentials and returns { ok, message } — nothing is persisted. It requires the caller be an org owner/admin.

POST /api/system/check

Prop

Type

Returns { components, ready, missing }.

POST /api/system/install

Prop

Type

POST /api/system/remove mirrors install for a single removable component. POST /api/system/install/stream takes { serverId, components: string[], config? } and returns a Server-Sent Events stream (progress, log, complete, end); it returns 409 install_in_progress if a session is already running.

# One-shot install:
openship server install <serverId> --component docker

# Stream logs live (attaches to POST /api/system/install/stream):
openship server install <serverId> --component docker --component openresty --follow

SSE, not JSON

install/stream and monitor/stream are event streams. Read GET /api/system/install/session to poll a running session's state instead, or GET /api/system/install/stream?id=<sessionId> to re-attach after a page reload.

GET /api/system/monitor/stream?serverId=<id> emits a stats event every few seconds with CPU %, memory, disk, uptime, and load average gathered over SSH — openship server monitor <serverId>.

Per-server rate limiting

Read or update the OpenResty request rate limit for a server. OpenResty on the target server is the source of truth; the API parses and rewrites its ratelimit.conf.

PATCH /api/system/servers/:id/rate-limit

Prop

Type

openship server rate-limit <serverId> --rps 50 --burst 100

Port-forward tunnels

Desktop-only. Forward a remote server port to localhost on the operator's machine, VS Code style. Every handler additionally asserts desktop mode; on a non-desktop instance they refuse.

POST /api/system/servers/:id/tunnels

Prop

Type

Start / stop / delete a saved tunnel with POST .../tunnels/:tunnelId/start, .../stop, and DELETE .../tunnels/:tunnelId. A started tunnel returns the actually-bound localPort and a http://localhost:<port> URL.

Browse the host filesystem

GET /api/system/browse?path=<dir> lists child directories of a path (defaults to the host's home directory), flagging which look like deployable projects. Used by the "deploy a local folder" picker.

openship system browse /srv/apps

Returns { path, directories: [{ name, path, isProject }] } — projects first, then alphabetical. Non-directory paths return 400, missing paths 404.

Team-mode migration

Move a single-user instance into team mode along one of three paths, or reverse it. All four routes require org admin/owner and refuse (409) unless teamMode === "single_user". See the cloud connect / migration guide.

Preflight and start (Path A — your own server) share a body:

POST /api/system/migration/start

Prop

Type

  • POST /api/system/migration/start-cloud — Path B, migrate to Openship Cloud. Body: { allowNonEmptyTarget?: boolean }.
  • POST /api/system/migration/start-tunnel — Path C, expose via an edge tunnel. Body: { slug: string } (required).
  • POST /api/system/migration/switch-back — reverse any of them. Body: { abandonRemote?: boolean }.
# Readiness check first, then migrate onto your server:
openship system migration preflight --server-id <id> --hostname app.example.com
openship system migration start --server-id <id> --hostname app.example.com

Switch-back cuts teammates off

Reversing to single-user mode revokes teammate access. The remote keeps a copy for a 30-day grace period so you can recover if you change your mind.

Whole-instance data transfer

Export or import the entire database — every organization's data. Both routes are owner-only (requireRole("owner") on top of the settings:admin tag), because the tag alone would also admit admins and members.

POST /api/system/data-transfer/export

Prop

Type

POST /api/system/data-transfer/import

Prop

Type

openship system data-transfer export --passphrase "$SECRET" --out instance.json
openship system data-transfer import --file instance.json --passphrase "$SECRET" --mode wipe

Import limits & errors

The import body is capped at 500 MB (413 PAYLOAD_TOO_LARGE above it). A wrong or missing passphrase or a malformed file returns 400; primary-key collisions in merge mode return 409 PK_COLLISION (use wipe, or clear the conflicting data first); a concurrent migration/import returns 503 BUSY.

On this page