Skip to main content

API reference

The API listens on port 2538 and answers in JSON.

Authentication​

ClientHowPermissions
BrowserSession after the OIDC login: signed authorization cookie (an Authorization: Bearer <token> header is also accepted)Every permission of the user
CLI, scripts, MCPAPI key in the X-API-Key headerThe user's profile, limited by the key's scopes
  • Profile: a user is admin or restricted. A key carries its user's profile: an admin scope on a restricted user's key grants nothing. Restricted users only see the containers they are granted.
  • Scopes: a key without scopes (legacy) has full access. * grants everything and cannot be combined with other scopes. A key can only create keys within its own scopes. Full list: API key scopes.
  • Tokens: loch_ followed by 64 hexadecimal characters, shown once, valid up to 365 days, at most 20 keys per user.
  • CSRF: write requests from a browser must carry the Origin of FRONT_URL, otherwise 403 FORBIDDEN_ORIGIN. Clients without a browser are not concerned.
  • Rate limit: 120 requests per minute per IP. The IP is read from X-Forwarded-For only behind a proxy listed in TRUSTED_PROXIES.

Errors​

Errors have the form:

{"code": "INSUFFICIENT_SCOPE", "error": "Insufficient API key scope"}

code is stable and translated by the interface; error is a readable fallback.

StatusCodeMeaning
400INVALID_REQUEST_BODYMalformed body; strict bodies also refuse unknown fields
401UNAUTHORIZEDNo session, or unknown API key
401API_KEY_EXPIREDThe API key has expired
403FORBIDDENInvalid session, or not allowed (not admin, container not granted)
403INSUFFICIENT_SCOPEThe API key lacks the scope
403FORBIDDEN_ORIGINBrowser write from another origin
429—Rate limit, plain text answer
500INTERNAL_ERRORServer error

Routes​

In the tables, Scope is the API key scope checked by the route; "—" means only the profile is checked. Admin means the route is reserved for admins.

Public​

MethodPathDescription
GET/_healthHealth check, includes the Docker status
GET/openid-connect/loginStart an OIDC login
GET/openid-connect/redirectOIDC callback
GET/openid-connect/logoutLog out

Containers and stacks​

MethodPathScopeAdminDescription
GET/dockercontainers:readContainers, filtered by permissions
GET/docker/{id}containers:readContainer details
POST/docker/{id}/{action}containers:actionsstart, stop, kill, restart, delete
POST/stacks/{name}/{action}containers:actionsstart, stop, restart every accessible container of a compose stack, in depends_on order
GET/logs/{id}containers:logsLogs (text)
GET/logs/{id}/jsoncontainers:logsLogs (JSON)
GET/logs/{id}/file?path=&tail=containers:logsLast lines of a file inside the container
GET/docker/{id}/cp?path=containers:cpDownload a file or directory (tar stream)
PUT/docker/{id}/cp?path=containers:cpUpload a file or directory (tar stream)
GET/wsper eventWebSocket, see below

Domains​

MethodPathScopeAdminDescription
GET/domains—YesDomains routed by Traefik

Scheduled tasks​

MethodPathScopeAdminDescription
GET/croncronTasks
POST/croncronCreate
GET/cron/{id}cronDetails
PUT / PATCH/cron/{id}cronUpdate / partial update
DELETE/cron/{id}cronDelete
POST/cron/{id}/runcronRun now (202)

Admins see every task. Restricted users see, change and delete every HTTP task and the Docker tasks of their containers; they can only create Docker tasks on those containers.

Task types: docker_action, docker_exec, http and function_invoke. A function_invoke task (functionInvoke: {function, event}, event at most 64 KiB, {} by default) is admin only; creating, changing or running one also needs functions:invoke. At most 5 tasks per function. Every task carries lastSkipped (a run skipped because the previous one was still running) and disabledReason (FUNCTION_DELETED when the panel disabled it).

Users and API keys​

MethodPathScopeAdminDescription
GET/users/me—Current user
GET/users/me/api-keys—Own keys, never the token
POST/users/me/api-keys—Create a key; the token is returned once
DELETE/users/me/api-keys/{id}—Revoke a key
GET/users—YesUsers
PATCH/users/{email}/switch—YesToggle admin / restricted
PATCH/users/{email}/restrict—YesMake restricted
PATCH/users/{email}—YesUpdate the container permissions
DELETE/users/{email}—YesDelete, and revoke the user's keys

SFTP users​

Available when SFTP_ENABLED is set (/sftp/config always answers).

MethodPathScopeAdminDescription
GET/sftp/config—YesPlugin status and connection details
GET/sftp/sitessftp:readYesSites
GET/sftp/userssftp:readYesUsers
POST/sftp/userssftp:writeYesCreate
PUT/sftp/users/{username}sftp:writeYesUpdate
DELETE/sftp/users/{username}sftp:writeYesDelete
POST/sftp/users/{username}/passwordsftp:writeYesNew password
POST/sftp/users/{username}/statussftp:writeYesEnable / disable

Web hosting​

Available when HOSTING_ENABLED is set (/hosting/config always answers).

MethodPathScopeAdminDescription
GET/hosting/config—YesPlugin status, templates, PHP versions, defaults, limits, defaultNetworks
GET/hosting/siteshosting:readYesSite directories and the hosting using each
GET/hosting/php-modules?template=&version=hosting:readYesPHP modules of an image
GET/hosting/routing-templatehosting:readYesRouting template
GET/hosting/stackshosting:readYesSites with their status
GET/hosting/stacks/check-name?name=hosting:readYesName availability
POST/hosting/stackshosting:writeYesCreate and deploy (202, asynchronous)
GET/hosting/stacks/{name}hosting:readYesSettings, containers, last operation. Without hosting:write, environment values and snippets are masked
PUT/hosting/stacks/{name}[?force=true]hosting:writeYesUpdate and redeploy, rolled back on failure (202)
POST/hosting/stacks/{name}/deploy[?pull=true][&force=true]hosting:writeYesRedeploy (202); force overwrites changes made outside the panel
DELETE/hosting/stacks/{name}[?purgeData=true]hosting:writeYesDelete; site data kept unless purgeData (202)

Managed services​

Available when SERVICES_ENABLED is set (/services/config always answers).

MethodPathScopeAdminDescription
GET/services/configservices:readYesModule status, engines, versions, options, exposure policy, version discovery state
GET/services/instancesservices:readYesInstances with state, networks, exposure, end of support, available updates
GET/services/instances/{instance}/resourcesservices:readYesResources of an instance
POST/services/resourcesservices:writeYesCreate {engine, version, name, options, legacyAuth, networks?}; returns the credentials once
POST/services/instances/{instance}/resources/{name}/passwordservices:writeYesNew password, returned once
DELETE/services/instances/{instance}/resources/{name}services:writeYesDelete a resource (a dedicated instance is deleted with it)
PATCH/services/instances/{instance}services:writeYesChange the mutable options of a dedicated instance {options}
POST/services/instances/{instance}/updateservices:writeYesUpdate a dedicated instance {version?}; returns {updated, version, image}
POST/services/updates/checkservices:writeYesDiscover versions and updates now, at most once per 30 seconds
PUT/services/instances/{instance}/networksnetworks:writeYesReplace the networks {networks}; the container is recreated
PUT/services/instances/{instance}/exposureservices:exposeYesPublish {address, port, tlsOptional?}
DELETE/services/instances/{instance}/exposureservices:exposeYesClose the port
DELETE/services/instances/{instance}[?purge=true]services:writeYesRemove an empty shared instance; purge deletes an instance edited by hand, with its data

services:expose is not implied by services:write.

Private networks​

Bodies are strict: unknown fields are refused (INVALID_REQUEST_BODY).

MethodPathScopeAdminDescription
GET/networksnetworks:readYesPrivate networks, the system network services first
GET/networks/confignetworks:readYesCreation rules
GET/networks/dockernetworks:readYesDocker networks the panel did not create (read-only)
GET/networks/docker/{id}networks:readYesOne of them, with its members
GET/networks/{name}networks:readYesA private network with its members
POST/networksnetworks:writeYesCreate {name, description?, subnet?} (201)
PATCH/networks/{name}networks:writeYesChange {description}
DELETE/networks/{name}networks:writeYesDelete (204)
POST/networks/{name}/membersnetworks:writeYesAttach {kind: "site" | "instance" | "function", name}; a function also needs functions:deploy
DELETE/networks/{name}/members/{kind}/{member}networks:writeYesDetach; a function also needs functions:deploy
POST/networks/{name}/disconnectnetworks:writeYesDisconnect a container the panel does not manage {containerId} (204)

Functions (work in progress)​

Available when FUNCTIONS_ENABLED is set (/functions/config always answers). Bodies are strict. Creating with env, networks, egress, sigv4 or mcpInvocable, or changing env, handler or mcpInvocable, needs functions:deploy on top of functions:write.

MethodPathScopeAdminDescription
GET/functions/configfunctions:readYesModule status, runtimes and versions, runtime binary state (runtime, runtimeReady), limits, budget used, defaults
GET/functionsfunctions:readYesFunctions with their state
POST/functionsfunctions:writeYesCreate {name, runtime, runtimeVersion, handler, memoryMB?, cpus?, timeoutSeconds?, tmpSizeMB?, env?, networks?, egress?, sigv4?, mcpInvocable?} (201); returns the invoke credentials once
GET/functions/{name}functions:readYesDetails: image, variables, secret names, deployments, endpoint; the container is verified
PATCH/functions/{name}functions:writeYesChange {memoryMB?, cpus?, timeoutSeconds?, tmpSizeMB?, handler?, env?, mcpInvocable?}; a running function is recreated
DELETE/functions/{name}[?purge=true]functions:writeYesDelete; purge removes a function edited outside the panel without running its files
POST/functions/{name}/codefunctions:deployYesDeploy a zip (application/zip body or multipart/form-data part code), activated at once (201)
GET/functions/{name}/deploymentsfunctions:readYesKept deployments, newest first
POST/functions/{name}/deployments/{id}/activatefunctions:deployYesActivate a kept deployment (204)
PUT/functions/{name}/secrets/{key}functions:deployYesSet a secret {value} (204); never returned
DELETE/functions/{name}/secrets/{key}functions:deployYesRemove a secret (204)
POST/functions/{name}/credentials/rotatefunctions:deployYesNew invoke credentials, returned once; optional {sigv4}
POST/functions/{name}/invokefunctions:invokeYesInvoke with the body as event (one JSON value, at most 6 MiB); always 200 with {statusCode, functionError?, requestId?, durationMs, payload? | payloadBase64?, logTail}
POST/functions/{name}/{action}functions:writeYesstart, stop, restart (204)
PUT/functions/{name}/networksfunctions:deployYesReplace the networks {networks} (never services); the container is recreated
PUT/functions/{name}/egressfunctions:deployYesInternet access {enabled}

What a function returns is untrusted: render it as text. Function containers are protected in the container routes: their environment is masked, and shell, file copy, file tail, port tunnel, container and stack actions and cron Docker tasks answer 403 FUNCTION_CONTAINER_PROTECTED. Their stdout logs stay readable by admins.

Audit trail​

MethodPathScopeAdminDescription
GET/audit?from=&to=&user=&event=&exclude=&level=&q=&before=&limit=audit:readYesEntries, newest first; limit up to 500

One request reads at most 64 MB of the log, so a page can be shorter than limit, even empty. Continue while the response has a next cursor, passed as before.

WebSocket​

Connect to /ws with the session cookie or the X-API-Key header. Messages are JSON {"event": ..., "data": ...}. The scope is checked on the events that open a session; a missing scope answers with the matching error event (error, logsError, statsError, proxyError, streamError).

Client eventScopeData
attachcontainers:shell{"id": "container-id"}
streamInput— (after attach)"command"
resize— (after attach){"cols": 120, "rows": 40}
logsStartcontainers:logs{"id": "container-id", "tail": 100, "file": "/optional/path"}
logsStop——
statsStartcontainers:stats{"ids": ["container-id-1", "container-id-2"]}
statsStop——
proxyOpencontainers:proxy{"id": "container-id", "mappings": [{"remote": 5432, "local": 15432}]}
streamOpencontainers:proxy{"streamID": 1, "remotePort": 5432}
streamClose—{"streamID": 1}

Server events: attach, stdout, stderr, end, logsData, logsEnd, logsError, statsData, statsError, proxyReady, streamOpened, streamClose, streamError, proxyError, error.

LimitValue
Port proxy dataBinary messages [streamID: uint32 big endian][payload]
Port proxyAt most 10 mappings per session and 256 concurrent streams
StatsCollected every 2 seconds, at most 20 containers per subscription, one statsStart every 3 seconds at most