Skip to content

API Reference

The Meshploy REST API. Built with Go, Chi router, and Huma for automatic OpenAPI 3.1 spec generation.


LanguageGo 1.25+
RouterChi
OpenAPIHuma v2 (OpenAPI 3.1, automatic schema + docs)
DatabaseGORM + PostgreSQL (via packages/db)
AuthJWT (HS256, 24h expiry) + TOTP 2FA; magt- agent tokens for automation

apps/api/ is a thin entrypoint — its main.go just calls server.Main(). The actual API core (config, HTTP handlers, business logic) lives in packages/server, which apps/api imports via a go.work replace directive. This split lets the API core be reused (e.g. embedded as the in-gateway /mcp server) without depending on the apps/api binary.

apps/api/
├── main.go # Calls server.Main() — no business logic here
├── Dockerfile
├── go.mod
└── tools/
packages/server/
├── server.go # HTTP server setup, route registration
├── entrypoint.go # Main() — config load, DB connect, server start
├── config/ # Typed env config — Load() from environment
├── middleware/ # Auth() resolves a JWT or magt- agent token; RequireAuth() 401s anything off the public allowlist
├── handler/ # HTTP layer only — thin, delegates to service
│ ├── handler.go # Handler struct, Register(), RegisterRaw()
│ ├── access.go # checkAccess(), checkOrgAdminAccess(), checkOrgMemberAccess() helpers
│ ├── auth.go # /auth/register, /auth/login, /me, TOTP, 2FA
│ ├── agent.go # Agent principals: create, list, token mint/rotate/revoke, delete
│ ├── mcp.go # Remote MCP (Streamable HTTP) at /mcp — agent-token authed, permission-scoped
│ ├── org.go # Org CRUD, member management, invitations
│ ├── project.go # Project CRUD
│ ├── permission.go # Per-resource permission grants
│ ├── node.go # Node CRUD, self-register, self-deregister, metrics
│ ├── workload.go # Service CRUD, env vars, build/db config, pods
│ ├── stack.go # Stack CRUD, apply, sync
│ ├── job.go # Job CRUD, trigger, run history
│ ├── volume.go # Volume CRUD, mounts, backup config
│ ├── route.go # Route CRUD, targets, hostname verify
│ ├── domain.go # Domain CRUD + DNS verification
│ ├── deployment.go # Deployment list, trigger, rollback, SSE logs
│ ├── backup.go # Service backups + system backup
│ ├── notification.go # Notification channels
│ ├── email_config.go # Org SMTP config
│ ├── variable_group.go # Variable group CRUD + service attach/detach
│ ├── git_integration.go # Git provider integrations + OAuth callbacks
│ ├── registry.go # Registry integration CRUD
│ ├── storage.go # Storage integration CRUD
│ ├── terminal.go # WebSocket: node terminal + pod terminal
│ ├── webhook.go # Inbound webhooks (GitHub push, deploy token)
│ ├── webhook_git.go # Inbound push webhooks for GitLab, Gitea/Forgejo, Bitbucket
│ ├── template.go # One-click template catalog
│ ├── config_file.go # Config file CRUD + attach/detach
│ ├── entitlement.go # Licence status + activation
│ ├── ondemand_tls.go # Caddy ask endpoint for on-demand TLS
│ ├── extension.go # Extension point: extra routes (EE)
│ ├── system.go # Version, exposure notice, install/uninstall scripts
│ └── health.go # GET /health
├── service/ # Business logic — one file per domain
│ ├── service.go # Services aggregate struct + New()
│ ├── auth.go # Register (user + default org in tx), Login, TOTP
│ ├── agent.go # Agent principals + agent_tokens; ResolveToken() for the auth middleware
│ ├── org.go # Org CRUD, member management, invitations
│ ├── project.go # Project CRUD
│ ├── permission.go # Resource permission grants
│ ├── node.go # Node CRUD, registration/provisioning tokens, monitor
│ ├── node_exporter.go # Live metrics scraping from node_exporter
│ ├── workload.go # Service CRUD, env vars, build/db config
│ ├── stack.go # Stack parse, apply, sync
│ ├── job.go # Job CRUD, trigger, reconciler goroutine
│ ├── volume.go # Volume CRUD, mounts, K8s PVC lifecycle
│ ├── route.go # Route + target CRUD
│ ├── domain.go # Domain CRUD + DNS verification
│ ├── deployment.go # Deployment trigger, rollback, K8s Job lifecycle
│ ├── backup.go # Backup schedule, trigger, restore, retention
│ ├── backup_executor.go # Backup/restore K8s Job execution
│ ├── notification.go # Notification dispatch (Slack, Discord, email, webhook)
│ ├── email_config.go # Org SMTP config
│ ├── variable_group.go # Variable group CRUD + service attachment
│ ├── git_integration.go # Git provider connections + OAuth flows
│ ├── registry.go # Registry integration CRUD
│ ├── storage.go # Storage integration CRUD
│ ├── db_explorer.go # Live DB query + schema via K8s exec
│ ├── system.go # Version info, install/uninstall script serving
│ ├── template.go # Template catalog fetch/cache + deploy
│ ├── config_file.go # Config files projected into workloads via Secrets
│ ├── exposure.go # Host-firewall exposure notice + dismissed notices
│ ├── entitlement.go # Licence verification + entitlements
│ ├── extension.go # Extension point: per-org quotas (EE)
│ ├── orphans.go # Cluster workloads that no service owns
│ ├── workload_status.go # Reconciles stored service status with the cluster
│ ├── volume_status.go # Reconciles stored volume status with its claim
│ ├── wsticket.go # Single-use tickets for WebSocket auth
│ └── headscale.go # Headscale API client (list, get, delete, rename nodes)
├── k8s/ # Kubernetes client helpers
├── templates/ # Built-in template assets
└── version/ # Build/version metadata

Routes are under /api/v1 unless listed under Outside /api/v1. Most need Authorization: Bearer <token>, where the token is a user’s JWT (24h) or an agent’s magt- token. The server is fail-closed: a route that is not on its public allowlist returns 401 without a token.

This lists every route registered in packages/server/handler. Auth: ✓ needs a bearer token, public needs none, otherwise the credential the route checks instead.

The OpenAPI spec is at /openapi.json and Huma’s docs page at /docs. Both need the same Authorization header, so fetch the spec with a token:

Terminal window
curl -H "Authorization: Bearer <token>" https://api.<your-domain>/openapi.json
MethodPathAuthDescription
POST/auth/loginpublicLogin and receive a JWT
POST/auth/recoverypublicComplete login with a one-time recovery code
POST/auth/registerpublicRegister a new user
GET/auth/statuspublicCheck whether registration is open (no users exist yet)
POST/auth/totppublicComplete login with TOTP code
GET/meGet current user
PATCH/me/passwordChange current user password
POST/me/recovery-codes/regenerateRegenerate 2FA recovery codes (requires current TOTP code)
DELETE/me/totpDisable 2FA (requires current TOTP code)
POST/me/totp/enableVerify TOTP code and enable 2FA
POST/me/totp/setupGenerate a new TOTP secret (not yet enabled)
MethodPathAuthDescription
GET/invitations/{token}invite tokenGet invitation info by token (public)
POST/invitations/{token}/acceptinvite tokenAccept an invitation and create an account (public)
GET/orgsList organizations for the authenticated user
POST/orgsCreate an organization, only on a server that has none: Community runs one per server (409 otherwise)
GET/orgs/{orgId}Get an organization
PATCH/orgs/{orgId}Update an organization
DELETE/orgs/{orgId}Delete an organization (owner only)
GET/orgs/{orgId}/invitationsList pending invitations
POST/orgs/{orgId}/invitationsCreate an invite link for a new member
GET/orgs/{orgId}/membersList organization members
POST/orgs/{orgId}/membersAdd a member to an organization
PATCH/orgs/{orgId}/members/{userId}Update a member’s role
DELETE/orgs/{orgId}/members/{userId}Remove a member from an organization
MethodPathAuthDescription
GET/orgs/{orgId}/jobs/{resourceId}/permissionsList permissions on a job
GET/orgs/{orgId}/members/{userId}/permissionsList all permission grants for a member
POST/orgs/{orgId}/members/{userId}/permissionsGrant a permission to a member
DELETE/orgs/{orgId}/members/{userId}/permissionsRevoke a permission from a member
GET/orgs/{orgId}/projects/{resourceId}/permissionsList permissions on a project
GET/orgs/{orgId}/services/{resourceId}/permissionsList permissions on a service
GET/orgs/{orgId}/stacks/{resourceId}/permissionsList permissions on a stack
MethodPathAuthDescription
GET/orgs/{orgId}/agentsList agent principals in an org
POST/orgs/{orgId}/agentsCreate an agent principal and mint its first token
DELETE/orgs/{orgId}/agents/{agentId}Delete an agent principal and all its tokens/grants
POST/orgs/{orgId}/agents/{agentId}/tokensMint an additional token for an agent (rotation)
DELETE/orgs/{orgId}/agents/{agentId}/tokens/{tokenId}Revoke an agent token
MethodPathAuthDescription
GET/entitlementsCurrent license entitlements for this install
POST/entitlements/licenseInstall a license token
MethodPathAuthDescription
GET/orgs/{orgId}/projectsList projects in an organization
POST/orgs/{orgId}/projectsCreate a project
GET/orgs/{orgId}/projects/{projectId}Get a project
PATCH/orgs/{orgId}/projects/{projectId}Update a project
DELETE/orgs/{orgId}/projects/{projectId}Delete a project
DELETE/orgs/{orgId}/projects/{projectId}/build-cacheClear the buildah layer cache PVC for a project
MethodPathAuthDescription
DELETE/nodes/self-deregisternode tokenSelf-deregister a node using its registration token and node ID
POST/nodes/self-registerregistration tokenSelf-register a node using a registration token
GET/orgs/{orgId}/cluster/headscale-preauth-keyGet the most recent active Headscale preauth key
POST/orgs/{orgId}/cluster/headscale-preauth-keyGenerate a new Headscale preauth key for joining the WireGuard mesh
GET/orgs/{orgId}/cluster/join-tokenGet the k3s node token for joining the cluster
GET/orgs/{orgId}/cluster/mesh-healthReport whether the control plane can reach Headscale
GET/orgs/{orgId}/cluster/orphansList cluster workloads that no service owns
DELETE/orgs/{orgId}/cluster/orphans/{namespace}/{name}Remove a cluster workload that no service owns
POST/orgs/{orgId}/node-provisioning-tokensCreate a single-use node provisioning token
GET/orgs/{orgId}/node-registration-tokenGet the node registration token
POST/orgs/{orgId}/node-registration-tokenGenerate (or rotate) the node registration token
GET/orgs/{orgId}/nodesList nodes in an organization
POST/orgs/{orgId}/nodesRegister a new node
GET/orgs/{orgId}/nodes/{nodeId}Get a node
PATCH/orgs/{orgId}/nodes/{nodeId}Update a node
DELETE/orgs/{orgId}/nodes/{nodeId}Remove a node from Headscale, the cluster and Meshploy, in that order. 200 {"removed": true} when it is gone; 202 {"removed": false, "error": ...} while Headscale has not confirmed the peer is removed, which is retried every minute. Calling it again retries at once
POST/orgs/{orgId}/nodes/{nodeId}/cancel-removalStop a node removal that is waiting for Headscale; 409 if none is
GET/orgs/{orgId}/nodes/{nodeId}/metricsGet live resource metrics for a node (requires node_exporter)
GET/orgs/{orgId}/nodes/{nodeId}/containersList containers running on a node that Meshploy does not manage
GET/orgs/{orgId}/discoveryList endpoints and containers on this org’s nodes that Meshploy does not route
POST/orgs/{orgId}/discovery/ignoresRecord that a discovered endpoint is known and correct as it is. Not called by the console: Discovery is a view of a machine, not a list to clear. Kept for a future “new endpoint appeared” notification, which needs a baseline
DELETE/orgs/{orgId}/discovery/ignores/{ignoreId}Remove that record
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/servicesList services in a project
POST/orgs/{orgId}/projects/{projectId}/servicesCreate a service
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}Get a service
PATCH/orgs/{orgId}/projects/{projectId}/services/{serviceId}Update a service
DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}Delete a service
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-configGet build config for a service
PATCH/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-configCreate or update build config for a service
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-config/deploy-tokenRegenerate the per-service webhook deploy token
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-config/env-varsGet build-time environment variables for a service
PUT/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-config/env-varsSet build-time environment variables for a service
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/database-configGet database config for a database service
PATCH/orgs/{orgId}/projects/{projectId}/services/{serviceId}/database-configChange a database’s network access
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/db/queryExecute a database query
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/db/schemaIntrospect database schema
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/env-varsGet decrypted env vars for a service
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/podsList running pods for a service
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/pods/metricsLive CPU and memory usage per pod (requires metrics-server)
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/resetWipe and re-provision a database (destructive)
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/startStart a service
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/stopStop a service
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deploymentsList deployments for a service
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deploymentsTrigger a new deployment
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}Get a deployment
DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}Cancel an active deployment
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}/logs/streamStream a deployment’s build log (SSE)
DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}/recordDelete a deployment record
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}/rollbackRoll back to a previous successful deployment
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/logsSnapshot of a service’s container logs
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/logs/streamStream a service’s container logs (SSE)
MethodPathAuthDescription
POST/orgs/{orgId}/projects/{projectId}/applyUpsert a stack from an inline compose manifest and reconcile it
GET/orgs/{orgId}/projects/{projectId}/stacksList stacks for a project
POST/orgs/{orgId}/projects/{projectId}/stacksCreate a new stack
POST/orgs/{orgId}/projects/{projectId}/stacks/meshploy-configWrite out every x-meshploy setting an apply would default
GET/orgs/{orgId}/projects/{projectId}/stacks/{stackId}Get a stack
PUT/orgs/{orgId}/projects/{projectId}/stacks/{stackId}Update a stack’s spec and variables
DELETE/orgs/{orgId}/projects/{projectId}/stacks/{stackId}Delete a stack
POST/orgs/{orgId}/projects/{projectId}/stacks/{stackId}/applyApply the stack spec - reconcile services
POST/orgs/{orgId}/projects/{projectId}/stacks/{stackId}/destroyDestroy the services this stack created, keeping the stack
GET/orgs/{orgId}/projects/{projectId}/stacks/{stackId}/servicesList services belonging to a stack
POST/orgs/{orgId}/projects/{projectId}/stacks/{stackId}/syncFetch spec from git source and re-apply
MethodPathAuthDescription
POST/orgs/{orgId}/projects/{projectId}/templates/{templateId}/deployDeploy a template into a project as a stack
GET/templatesList one-click templates
POST/templates/refreshRe-read the template catalog from its source
GET/templates/{templateId}Get a template (manifest + compose)
GET/templates/{templateId}/iconpublicTemplate icon image
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/jobsList jobs in a project
POST/orgs/{orgId}/projects/{projectId}/jobsCreate a job or cron job
GET/orgs/{orgId}/projects/{projectId}/jobs/{jobId}Get a job
PATCH/orgs/{orgId}/projects/{projectId}/jobs/{jobId}Update a job
DELETE/orgs/{orgId}/projects/{projectId}/jobs/{jobId}Delete a job
GET/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/runsList run history for a job
DELETE/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/runs/{runId}Delete a job run record
POST/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/triggerManually trigger a job run
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/mountsList a service’s volume mounts
GET/orgs/{orgId}/projects/{projectId}/volumesList volumes
POST/orgs/{orgId}/projects/{projectId}/volumesCreate a volume
GET/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}Get a volume
DELETE/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}Delete a volume (must be unattached)
GET/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/backupGet a volume’s backup config
PUT/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/backupSet a volume’s backup config
DELETE/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/backupRemove a volume’s backup config
POST/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/mountsAttach a volume to a service
DELETE/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/mounts/{mountId}Detach a volume mount
PUT/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/nodePin a volume to a node, or clear the pin to auto-schedule
GET/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/placementWhere the volume’s claim is actually bound or pinned
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/variable-groupsList variable groups attached to service
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/variable-groupsAttach variable group to service
DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}/variable-groups/{groupId}Detach variable group from service
GET/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/variable-groupsList variable groups attached to a job
POST/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/variable-groupsAttach a variable group to a job
DELETE/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/variable-groups/{groupId}Detach a variable group from a job
GET/orgs/{orgId}/projects/{projectId}/variable-groupsList variable groups
POST/orgs/{orgId}/projects/{projectId}/variable-groupsCreate variable group
GET/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}Get variable group
PATCH/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}Update variable group
DELETE/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}Delete variable group
PUT/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}/itemsUpsert variable group item
GET/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}/items/{itemId}/valueReveal one item’s value. A list never carries a secret’s value, so reading one is its own request, gated on project update rather than view
DELETE/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}/items/{itemId}Delete variable group item
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/config-filesList the project’s config files
POST/orgs/{orgId}/projects/{projectId}/config-filesCreate a config file
GET/orgs/{orgId}/projects/{projectId}/config-files/{fileId}Get a config file and the services mounting it
PATCH/orgs/{orgId}/projects/{projectId}/config-files/{fileId}Replace a config file’s content, re-applying every service using it
DELETE/orgs/{orgId}/projects/{projectId}/config-files/{fileId}Delete a config file that no service mounts
POST/orgs/{orgId}/projects/{projectId}/config-files/{fileId}/attach/{serviceId}Mount a config file into a service
DELETE/orgs/{orgId}/projects/{projectId}/config-files/{fileId}/attach/{serviceId}Unmount a config file from a service
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/routesList routes in a project
POST/orgs/{orgId}/projects/{projectId}/routesCreate a route
GET/orgs/{orgId}/projects/{projectId}/routes/{routeId}Get a route
DELETE/orgs/{orgId}/projects/{projectId}/routes/{routeId}Delete a route
POST/orgs/{orgId}/projects/{projectId}/routes/{routeId}/targetsAdd a path target to a route
PATCH/orgs/{orgId}/projects/{projectId}/routes/{routeId}/targets/{targetId}Update a route target
DELETE/orgs/{orgId}/projects/{projectId}/routes/{routeId}/targets/{targetId}Delete a route target
POST/orgs/{orgId}/projects/{projectId}/routes/{routeId}/moveMove a route to another base domain, keeping its subdomain, zone and targets; keep_redirect leaves the old hostname answering with a 301
POST/orgs/{orgId}/projects/{projectId}/routes/{routeId}/verify-hostnameVerify DNS ownership of a custom-domain route via TXT record
POST/orgs/{orgId}/projects/{projectId}/routes/{routeId}/publishServe a paused route again
POST/orgs/{orgId}/projects/{projectId}/routes/{routeId}/pauseKeep a route, answer 404 and issue no certificate. Create one paused with published: false
GET/orgs/{orgId}/routesList all routes in an organization
GET/orgs/{orgId}/tcp-routesList every published TCP port in an organization, with the gateway’s own
GET/orgs/{orgId}/projects/{projectId}/tcp-routesList TCP routes in a project
POST/orgs/{orgId}/projects/{projectId}/tcp-routesPublish a TCP port on the gateway
GET/orgs/{orgId}/projects/{projectId}/tcp-routes/{routeId}Get a TCP route
POST/orgs/{orgId}/projects/{projectId}/tcp-routes/{routeId}/publishOpen a paused TCP route’s port again
POST/orgs/{orgId}/projects/{projectId}/tcp-routes/{routeId}/pauseClose a TCP route’s port and keep the route
PATCH/orgs/{orgId}/projects/{projectId}/tcp-routes/{routeId}Change a TCP route’s port or who may connect
DELETE/orgs/{orgId}/projects/{projectId}/tcp-routes/{routeId}Stop publishing a TCP port
MethodPathAuthDescription
GET/internal/domain-checkpublic (Caddy)Caddy ask endpoint for On-Demand TLS
GET/internal/ondemand-tls-checkpublic (Caddy)Caddy ask endpoint for On-Demand TLS (self-managed DNS mode)
GET/orgs/{orgId}/domainsList domains for an organization (primary first)
POST/orgs/{orgId}/domainsAdd a base domain
GET/orgs/{orgId}/domains/{domainId}Get a domain
POST/orgs/{orgId}/domains/{domainId}/verifyCheck the ownership TXT record for a domain
GET/orgs/{orgId}/domains/{domainId}/routesHostnames served under a base domain, across every project
POST/orgs/{orgId}/domains/{domainId}/retireStart retiring a base domain: no new routes, everything already on it keeps serving
DELETE/orgs/{orgId}/domains/{domainId}/retireStop retiring a base domain
POST/orgs/{orgId}/domains/{domainId}/make-primaryMove the primary here. The old primary keeps serving its console, api and headscale names until it is removed
GET/orgs/{orgId}/domains/{domainId}/nodesNodes whose control connection goes through this domain’s headscale name - what stops a former primary being removed
POST/orgs/{orgId}/nodes/{nodeId}/control-movedRecord that a node now reaches Headscale through the primary. Records what the operator did on the machine; it does not move it
GET/orgs/{orgId}/domains/{domainId}/integrationsGit provider registrations - a GitHub App’s URLs, an OAuth redirect, repository push hooks - still pointing at this domain
POST/orgs/{orgId}/git-integrations/{id}/move-hooksMove an integration’s repository push hooks to the primary’s API address through the provider’s API, per repository
POST/orgs/{orgId}/git-integrations/{id}/registration-updatedRecord that a registration was changed at the provider by hand. For an OAuth redirect, Meshploy starts sending the new URI
GET/orgs/{orgId}/domains/{domainId}/deploy-hooksServices whose CI deploy webhook was last called through this domain, as observed from where calls arrive
DELETE/orgs/{orgId}/services/{serviceId}/deploy-hook-callForget a deploy webhook’s last caller, for a CI job that no longer exists. A later call records it again
GET/orgs/{orgId}/custom-domainsHostnames that belong to a route rather than a base domain
PATCH/orgs/{orgId}/domains/{domainId}/dns-modeChange how a domain’s DNS is arranged
DELETE/orgs/{orgId}/domains/{domainId}Remove a base domain. A verified one must be retiring and hold no routes; no node may still reach the mesh through it, no git provider may still call it, and no CI job may still deploy through it. An unverified one can go at once

Verifying a domain, changing its DNS mode or removing one records the new domain set in the host agent’s inbox and asks it to regenerate Caddy and CoreDNS. The API writes no configuration itself and reloads nothing: it has no root on the host and no Docker socket.

MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backupsList backup configs for a service
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backupsAdd a backup config for a service
PATCH/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}Update a backup config
DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}Delete a backup config
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}/objectsList restore points for a backup config
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}/restoreRestore a database from a backup object
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}/triggerManually trigger a backup
GET/orgs/{orgId}/system-backupGet system backup config for an org
PUT/orgs/{orgId}/system-backupCreate or update system backup config
DELETE/orgs/{orgId}/system-backupDelete system backup config
GET/orgs/{orgId}/system-backup/objectsList restore points for the system backup
POST/orgs/{orgId}/system-backup/restoreRestore system database from a backup object
POST/orgs/{orgId}/system-backup/triggerManually trigger the system backup
MethodPathAuthDescription
GET/orgs/{orgId}/email-configGet the org SMTP configuration
PUT/orgs/{orgId}/email-configCreate or update the org SMTP configuration
DELETE/orgs/{orgId}/email-configRemove the org SMTP configuration
POST/orgs/{orgId}/email-config/testSend a test email to one address; a failed send returns success: false and the error
GET/notification-eventsList the events a notification channel can subscribe to
GET/orgs/{orgId}/notification-channelsList notification channels
POST/orgs/{orgId}/notification-channelsCreate a notification channel
PUT/orgs/{orgId}/notification-channels/{id}Update a notification channel
DELETE/orgs/{orgId}/notification-channels/{id}Delete a notification channel
POST/orgs/{orgId}/notification-channels/{id}/testSend a test notification and record the attempt
GET/orgs/{orgId}/notification-channels/{id}/deliveriesA channel’s delivery attempts, newest first (?status=failed, ?limit=, ?before=<timestamp> to page back)
POST/orgs/{orgId}/notification-deliveries/{id}/retrySend a recorded attempt again
MethodPathAuthDescription
GET/gitea/callbackOAuth stateGitea OAuth callback
GET/github/app-callbackOAuth stateGitHub App installation callback
GET/github/callbackOAuth stateGitHub OAuth callback
GET/gitlab/callbackOAuth stateGitLab OAuth callback
GET/orgs/{orgId}/git-integrationsList git integrations
POST/orgs/{orgId}/git-integrationsCreate a GitLab, Gitea or Bitbucket integration from a token
POST/orgs/{orgId}/git-integrations/githubStart a GitHub App integration (manifest flow)
POST/orgs/{orgId}/git-integrations/oauthStart a GitLab, Gitea or Bitbucket OAuth App connection
DELETE/orgs/{orgId}/git-integrations/{id}Delete a git integration
GET/orgs/{orgId}/git-integrations/{id}/branchesList branches for a repository
GET/orgs/{orgId}/git-integrations/{id}/install-urlGet GitHub App install URL for a specific integration
GET/orgs/{orgId}/git-integrations/{id}/oauth-reconnectRe-generate OAuth authorization URL for a pending integration
GET/orgs/{orgId}/git-integrations/{id}/push-hookWhere the provider should deliver pushes, and the secret to sign them with (org admin)
GET/orgs/{orgId}/git-integrations/{id}/reposList repositories for a git integration
GET/orgs/{orgId}/registry-integrationsList container registry integrations
POST/orgs/{orgId}/registry-integrationsAdd a container registry integration
DELETE/orgs/{orgId}/registry-integrations/{id}Remove a container registry integration
GET/orgs/{orgId}/storage-integrationsList object storage integrations
POST/orgs/{orgId}/storage-integrationsAdd an object storage integration
DELETE/orgs/{orgId}/storage-integrations/{id}Remove an object storage integration
MethodPathAuthDescription
GET/orgs/{orgId}/nodes/{nodeId}/terminalticketWebSocket: shell on a node
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/pods/{podName}/terminalticketWebSocket: shell in a pod
POST/terminal/ticketMint a single-use ticket for a terminal WebSocket
MethodPathAuthDescription
POST/webhooks/deploy/{serviceId}deploy tokenInbound deploy webhook
POST/webhooks/github/{integrationId}HMACInbound GitHub push webhook
POST/webhooks/git/{provider}/{integrationId}HMAC / tokenInbound push webhook for GitLab, Gitea/Forgejo and Bitbucket
MethodPathAuthDescription
GET/system/exposureReport whether this gateway runs without a host firewall
POST/system/notices/{key}/dismissDismiss a console advisory for the current user
GET/system/channelsDescribe the stable and edge channels, where this server is, and whether it may switch
GET/system/upgradeReport whether this server can be upgraded from the console, and the last upgrade
POST/system/upgradeQueue an upgrade of this server to the latest build on its channel, a switch to the other channel ({"channel": "edge"}), or a switch to the Enterprise images the active licence grants ({"edition": "enterprise"}). Instance owner only
GET/system/versionGet current and latest platform version
GET/system/migrate/dokployInstance owner: the host agent’s latest Dokploy detection and plan, and the state of each request
POST/system/migrate/dokploy/{kind}Instance owner: run a stage of the migration. detect and plan are read-only on the host. credential mints the migration agent’s token and leaves it in the inbox for the agent to take once. prepare is stage 1 - the Meshploy side, services stopped and routes paused, Dokploy untouched. move is stage 2 for one group (body: group) and is the first kind that stops anything. cutover is stage 3, handing over ports 80 and 443. rollback undoes one group, or everything when no group is given; 409 when the agent is not reporting
GET/system/host-agentWhether the gateway’s host agent is reporting, its version and the firewall it found
POST/system/check-updatesAsk GitHub for the newest release or build now, past the caches; at most one real check every 30 seconds
MethodPathAuthDescription
GET/healthpublicHealth check
GET/install.shInstall script
GET/POST/mcpagent tokenRemote MCP (Streamable HTTP), permission-scoped
GET/uninstall.shUninstall script

GET /orgs/{orgId}/nodes enriches each node with live Headscale peer data (online status, last seen, FQDN). When a node has a stored headscale_id the lookup is O(1). Nodes without an ID fall back to an IP scan and store the ID as a side-effect for future calls.

Worker nodes authenticate with a registration or provisioning token rather than a user JWT:

  • Self-register — called by install.sh. Accepts either a mreg-<hex> registration token (reusable, org-wide) or a mprov-<hex> provisioning token (single-use, with expiry). Creates the node record and returns the node ID.
  • Self-deregister — called by uninstall.sh. Removes the node from Headscale, the k3s cluster, and the database.

VariableRequiredDescription
DATABASE_URLYesPostgreSQL DSN
JWT_SECRETYesSecret for signing JWTs
ENCRYPTION_KEYYesExactly 32 characters: AES-256-GCM field encryption
API_PORTNoListen port (default: 4000)
API_BASE_URLNoPublic base URL of the API (default: http://localhost:4000)
FRONTEND_URLNoConsole URL (default: http://localhost:5173)
SETUP_TOKENNoGates the first registration; set by install.sh. Empty disables the check
HEADSCALE_URLNoHeadscale API URL
HEADSCALE_API_KEYNoHeadscale API key
HEADSCALE_USERNoHeadscale user pre-auth keys are created under (default: meshploy)
KUBECONFIGNoPath to kubeconfig; empty = in-cluster
K3S_SERVER_URLNoOverride the k3s API URL (needed when the API runs in Docker)
K3S_TLS_SERVER_NAMENoName the cluster certificate is verified against when K3S_SERVER_URL rewrites the address
K3S_SKIP_TLS_VERIFYNoEscape hatch: disables authentication of the cluster connection. Leave unset
K3S_TOKENNoNode token for workers joining the cluster
BUILDER_IMAGENoOverride the builder container image
DOMAINNoBase domain; seeds the org domain record
MESH_IPNoGateway’s WireGuard mesh IP; seeds the gateway node
GATEWAY_HOSTNAMENoGateway hostname, used for gateway node seeding
PUBLIC_IPNoGateway public IP, backfilled on the gateway node record
HOST_GATEWAY_IPNoDocker bridge IP, used when the API runs in Docker to reach the gateway’s node_exporter
FIREWALL_STATENoWhat install.sh saw on the host: none, ufw or firewalld. Drives the console’s exposure notice
NODEPORT_ADDRESSESNoThe CIDR kube-proxy binds published ports to, e.g. 100.64.0.0/10 for the mesh. Empty means every interface, which the console warns about when a database is published
FIREWALL_CHECKED_ATNoWhen install.sh checked the firewall (RFC3339)
BUILTIN_REGISTRY_ENDPOINTNoSeed a built-in registry row per org (<host>:<port>)
TEMPLATE_DIRNoLocal template catalog directory; overrides the remote repo
TEMPLATE_REPONoGitHub owner/repo the catalog is fetched from (default: meshploy/meshploy-templates)
TEMPLATE_REPO_REFNoGit ref for the catalog repo (default: main)
TEMPLATE_REFRESH_INTERVALNoHow often the catalog cache refreshes (default: 1h)

Terminal window
cd apps/api
go run main.go

API at http://localhost:4000. The OpenAPI spec is at /openapi.json and needs a bearer token, like every non-public route.

Database migrations run automatically on startup via db.Migrate().