Search the docs

Find a page or a section of the docs

Deploy now

API

The JSON API under /api/v1

Markdown

The CLI and the terminal UI use this API, and so can your own scripts. Where things work shows which endpoints answer only a signed-in browser. Its OpenAPI 3.1 document is served at /api/v1/openapi.yaml.

Send a personal API token as Authorization: Bearer pat_…. The token belongs to one workspace, and every endpoint below works in it. Errors are {"error":{"code","message","fields"}} with stable codes such as server_offline, deploy_running and no_rollback.

Endpoints marked session answer only a signed-in browser, never a token, so a leaked token cannot make new tokens, change roles, remove people or delete a workspace. Their sign-in and two-step flows live under /api/v1/auth; GET /api/v1/csrf gives the CSRF token a session sends with every change.

A user can hold 20 server-sent event streams open at once, across all …/events endpoints. One more answers 429 with rate_limited; closing a stream frees its place.

Endpoint Does
GET /api/v1/meta the name, version and support address of this control plane; needs no token
GET /api/v1/version the version and, on a self-hosted control plane, the newest release, its “In short” line, the notes and how the last update went
POST /api/v1/version/update updates a self-hosted control plane to the newest release (admins in ADMIN_EMAILS); follow it with GET /api/v1/version
GET /api/v1/account · PATCH /account/name the signed-in account; its name
PUT /api/v1/account/email-alerts turns your email alerts on or off {"on": false}; GET /api/v1/account has them as email_alerts
PATCH /api/v1/account/email · POST /account/password · DELETE /account email, password, deleting the account (session)
POST /api/v1/account/password/set · DELETE /account/password sets the first password; removes it (session)
POST · DELETE /api/v1/account/avatar the profile picture
POST /api/v1/auth/device · POST /api/v1/auth/device/token moin login through the browser (RFC 8628): start a login with {"hostname"}, then poll with {"device_code"} every interval seconds until it answers access_token; needs no token. See Log in the CLI
GET · POST /api/v1/account/tokens · DELETE /{id} personal API tokens and the computers logged in with moin login, kind personal or device (session)
GET /api/v1/account/sessions · DELETE /{id} · DELETE signed-in browsers; signs one or all others out (session)
GET /api/v1/activity who did what in the workspace, newest first, with next_cursor; app, server and user filter it
GET /api/v1/workspaces · POST the workspaces you are in; a new one (session)
GET /api/v1/workspaces/{slug} the team: members, roles and open invitations
GET /api/v1/workspaces/{slug}/activity the same as GET /api/v1/activity, for the workspace in the path
GET /api/v1/invitations/{token} · POST /{token}/accept an invitation from its email link; accepting it (session)
PATCH /api/v1/workspaces/{slug}/name renames the workspace (admin)
DELETE /api/v1/workspaces/{slug} deletes the workspace (owner, session)
POST /api/v1/workspaces/{slug}/invitations invites someone by email (admin)
POST /api/v1/workspaces/{slug}/invitations/{id}/resend · DELETE /{id} sends an invitation again; revokes it (admin)
PATCH /api/v1/workspaces/{slug}/members/{id}/role changes a role (admin; only an owner makes or changes an owner, session)
DELETE /api/v1/workspaces/{slug}/members/{id} removes a member (admin; only an owner removes an owner, session)
POST /api/v1/workspaces/{slug}/leave leaves the workspace (session)
GET /api/v1/workspaces/{slug}/billing the plan and the subscription, where billing is on
POST /api/v1/workspaces/{slug}/billing/checkout · GET /billing/manage a checkout link; the billing portal (owner, session)
GET /api/v1/servers · POST servers, with their rules from /etc/moin/rules
GET /api/v1/servers/events server list as server-sent events
POST /api/v1/servers/{id}/install a new one-time install command; the old one stops working
PATCH /api/v1/servers/{id} renames a server {"name": "…"}
PUT /api/v1/servers/order puts the servers in a new order {"order": ["web-1", "db-1"]}, every server once, by name or ID; answers the list in it
DELETE /api/v1/servers/{id} removes a server; answers the uninstall_command that removes its agent there, empty when no agent ever connected, and the apps that went with an offline server
POST /api/v1/servers/{id}/update updates the agent to the control plane’s version
GET /api/v1/servers/{id}/metrics?range=1h CPU, memory, disk, network and load of the server now and over a range, and its apps now, the most memory first
GET /api/v1/agent-updates · PUT whether agents update by themselves {"auto": true}; on by default
GET /api/v1/status whether everything is OK: ok, the issues that need you with the path to fix them, catalog updates and counts (Overview)
GET /api/v1/catalog · GET /{slug} the catalog with each app’s newest release, or one app with every release
POST /api/v1/catalog/{slug}/install installs a catalog app {"server": "web-1", "version": "3.4.1", "name": "stats", "domain": "stats.example.com"}; answers the app and its deploy
POST /api/v1/apps/{app}/update sets a catalog app to a release {"version": "3.4.1"}, the newest when empty, and deploys it
GET /api/v1/apps · POST · GET /{app} · DELETE /{app} apps
GET /api/v1/apps/events app list as server-sent events
PUT /api/v1/apps/order puts the apps in a new order {"order": ["blog", "shop"]}, every app once, by name or ID; answers the list in it
POST /api/v1/apps/{app}/uploads a link that takes the source straight into your bucket
POST /api/v1/apps/{app}/deploys deploys an upload {"upload": "…", "sha256": "…"}, an image {"image": "…"} built in CI, or {"ref": "main"} from GitHub
POST /api/v1/apps/{app}/hook a new deploy hook URL, shown once
POST /webhooks/deploy/{token} the deploy hook CI calls with ?image= or the JSON body {"image": "…"}
GET /api/v1/registries · PUT · DELETE /{host} registry logins for private images
GET /api/v1/apps/{app}/deploys/{number} a deploy with its log
GET /api/v1/apps/{app}/deploys/{number}/events a deploy as server-sent events
POST /api/v1/apps/{app}/redeploy builds the newest source again, or runs the newest image again
POST /api/v1/apps/{app}/rollback starts an earlier release again
GET /api/v1/apps/{app}/logs container logs
POST /api/v1/apps/{app}/exec runs a command as server-sent events: output pieces, then done with exit_code, timed_out and truncated; takes {"command": [...]} or {"line": "..."}, service and timeout in seconds (owner or admin)
PUT /api/v1/servers/{id}/security Cloudflare Tunnel, SSH on Tailscale, firewall
GET /api/v1/alert-destinations · POST · DELETE /{name} where alerts go besides email: Slack, Discord, Telegram or a webhook; POST takes {"kind", "name", "url"}, or bot_token and chat_id for Telegram, and events; a webhook answers with its signing_secret once (owner or admin to change)
POST /api/v1/alert-destinations/{name}/test sends a test alert; 424 with alert_send_failed and the reason when the destination refuses it (owner or admin)
GET · PUT /api/v1/integrations the Cloudflare API token and the Tailscale auth key
GET /api/v1/github · GET /api/v1/github/repos GitHub installations with their settings_url and the install link; repos moin reads
GET /api/v1/github/repos/{owner}/{repo}/branches the branches of a repo, the default first
PUT · DELETE /api/v1/apps/{app}/repo links or unlinks a GitHub repo and branch
GET /api/v1/apps/{app}/databases Databases of the live release: SQLite files and Postgres services
GET /api/v1/apps/{app}/databases/tables tables, columns, row counts, schema and migration
GET · POST · PATCH · DELETE /api/v1/apps/{app}/databases/rows browse, add, edit and delete rows
POST /api/v1/apps/{app}/databases/query runs SQL; write: true for changes
GET · POST /api/v1/apps/{app}/backups backups; a snapshot now
PUT /api/v1/apps/{app}/backups/settings snapshot before every change
POST /api/v1/apps/{app}/restore restores a snapshot or a point in time; bucket names an older bucket the snapshot is in
GET /api/v1/apps/{app}/uptime uptime per domain: state, 24 hours, 7 and 30 days, the newest check and the last outages
PUT /api/v1/apps/{app}/uptime/{domain} the path of a domain’s check {"path": "/health"}, or pauses it {"paused": true}
GET /api/v1/apps/{app}/metrics?range=1h CPU, memory and network of the app now and over 1h, 24h, 7d or 30d, with the deploys that went live
GET · POST /api/v1/buckets the workspace’s buckets, the default first, with the apps that use each; adds one
GET · PUT · DELETE /api/v1/buckets/{name} one bucket; changes it (an empty secret_key keeps the stored one, default: true makes it the default); removes it, 409 bucket_in_use while apps use it or it keeps older backups
PUT /api/v1/apps/{app}/bucket the app’s bucket {"bucket": "client-a"}, or "" for the default; older backups stay in the old bucket
GET · PUT · DELETE /api/v1/backup-storage the default bucket, for older clients
GET · PUT /api/v1/apps/{app}/domains DNS checks with the server’s ipv4, ipv6 and the record to set as advice; domains and canonical, or with service the domains of another service
GET · PUT /api/v1/apps/{app}/instant the instant address, its provider and password (?show=true with a token that may write); pick the provider or off, and open
PUT · DELETE /api/v1/apps/{app}/protection password protection
POST /api/v1/apps/{app}/restart starts the live release again with the current settings
POST /api/v1/apps/{app}/stop takes the app offline; its data stays
POST /api/v1/apps/{app}/start runs a stopped app again
GET · PATCH · PUT /api/v1/apps/{app}/env env vars; PUT replaces them all at once and answers the keys added, changed and removed