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.22+
RouterChi
OpenAPIHuma v2 (OpenAPI 3.1, automatic schema + docs)
DatabaseGORM + PostgreSQL (via packages/db)
AuthJWT (HS256, 24h expiry) + TOTP 2FA

apps/api/
├── main.go
└── internal/
├── config/ # Typed env config — Load() from environment
├── middleware/ # Auth() — soft JWT middleware, sets user in ctx
├── 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
│ ├── 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)
│ ├── system.go # System version, 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
├── 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
└── headscale.go # Headscale API client (list, get, delete, rename nodes)

All routes are under /api/v1. Authenticated routes require Authorization: Bearer <jwt>. The interactive OpenAPI docs are at GET /docs.

MethodPathAuthDescription
POST/auth/registerCreate user + default org
POST/auth/loginReturn signed JWT (prompts TOTP if enabled)
POST/auth/totpComplete TOTP step during login
POST/auth/recoveryLogin with a recovery code
GET/auth/statusCheck if any users exist (onboarding gate)
GET/meCurrent user profile
PUT/me/passwordChange password
GET/POST/me/totp/setupBegin TOTP enrollment (returns QR seed)
POST/me/totp/enableConfirm and activate TOTP
DELETE/me/totpDisable TOTP
POST/me/recovery-codes/regenerateRegenerate recovery codes
MethodPathAuthDescription
GET/POST/orgsList / create orgs
GET/PATCH/DELETE/orgs/{orgId}Get / update / delete org
GET/POST/orgs/{orgId}/membersList / add members
PATCH/DELETE/orgs/{orgId}/members/{userId}Update role / remove member
POST/orgs/{orgId}/invitationsSend email invitation
GET/orgs/{orgId}/invitationsList pending invitations
GET/invitations/{token}Look up invitation by token
POST/invitations/{token}/acceptAccept an invitation
GET/POST/DELETE/orgs/{orgId}/members/{userId}/permissionsList / grant / revoke resource permissions
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projectsList / create projects
GET/PUT/DELETE/orgs/{orgId}/projects/{projectId}Project CRUD
DELETE/orgs/{orgId}/projects/{projectId}/build-cachePurge build cache for project
GET/orgs/{orgId}/projects/{resourceId}/permissionsProject-level permissions
MethodPathAuthDescription
GET/POST/orgs/{orgId}/nodesList / register nodes
GET/PUT/DELETE/orgs/{orgId}/nodes/{nodeId}Node CRUD
GET/orgs/{orgId}/nodes/{nodeId}/metricsLive CPU / memory / disk metrics
POST/nodes/self-registerWorker self-registration (mreg- or mprov- token)
DELETE/nodes/self-deregisterWorker self-removal
GET/POST/orgs/{orgId}/node-registration-tokenGet / rotate registration token
GET/POST/orgs/{orgId}/node-provisioning-tokensList / create provisioning tokens
GET/cluster/headscale-preauth-key✓ adminGenerate Headscale pre-auth key
GET/cluster/join-token✓ adminGet K3s join token
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projects/{projectId}/servicesList / create services
GET/PUT/DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}Service CRUD
GET/PUT/orgs/{orgId}/projects/{projectId}/services/{serviceId}/env-varsGet / update env vars
GET/PUT/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-configBuild config
PATCH/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-config/env-varsBuild-time env vars
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/build-config/deploy-tokenRegenerate deploy webhook token
GET/PUT/orgs/{orgId}/projects/{projectId}/services/{serviceId}/database-configDatabase config
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/startScale up
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/stopScale to zero
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/resetReset database (destructive)
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/podsList running pods
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/pods/metricsPod CPU/memory
GET/orgs/{orgId}/services/{resourceId}/permissionsService-level permissions
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deploymentsList / trigger deployment
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}Get deployment
DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}Delete record
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/deployments/{deploymentId}/rollbackRoll back
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/logsContainer log snapshot
GET…/deployments/{deploymentId}/logs/streamSSE build log stream
GET…/services/{serviceId}/logs/streamSSE live container log stream
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projects/{projectId}/stacksList / create stacks
GET/PUT/DELETE/orgs/{orgId}/projects/{projectId}/stacks/{stackId}Stack CRUD
POST/orgs/{orgId}/projects/{projectId}/stacks/{stackId}/applyApply — create/update services from spec
POST/orgs/{orgId}/projects/{projectId}/stacks/{stackId}/syncSync spec from git
GET/orgs/{orgId}/projects/{projectId}/stacks/{stackId}/servicesList stack-owned services
GET/orgs/{orgId}/stacks/{resourceId}/permissionsStack-level permissions
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projects/{projectId}/jobsList / create jobs
GET/PUT/DELETE/orgs/{orgId}/projects/{projectId}/jobs/{jobId}Job CRUD
POST/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/triggerTrigger a run
GET/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/runsRun history
DELETE/orgs/{orgId}/projects/{projectId}/jobs/{jobId}/runs/{runId}Delete run record
GET/orgs/{orgId}/jobs/{resourceId}/permissionsJob-level permissions
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projects/{projectId}/volumesList / create volumes
GET/DELETE/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}Get / delete volume
POST/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/mountsAttach to service
DELETE/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/mounts/{mountId}Detach mount
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/mountsList mounts for service
GET/PUT/DELETE/orgs/{orgId}/projects/{projectId}/volumes/{volumeId}/backupVolume backup config
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projects/{projectId}/variable-groupsList / create groups
GET/PATCH/DELETE/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}Group CRUD
PUT/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}/itemsUpsert item
DELETE/orgs/{orgId}/projects/{projectId}/variable-groups/{groupId}/items/{itemId}Delete item
GET/POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/variable-groupsList / attach group
DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}/variable-groups/{groupId}Detach group
MethodPathAuthDescription
GET/orgs/{orgId}/routesList all routes across projects
GET/POST/orgs/{orgId}/projects/{projectId}/routesList / create routes
GET/DELETE/orgs/{orgId}/projects/{projectId}/routes/{routeId}Get / delete route
POST/orgs/{orgId}/projects/{projectId}/routes/{routeId}/verify-hostnameVerify DNS record
POST/orgs/{orgId}/projects/{projectId}/routes/{routeId}/targetsAdd route target
PATCH/DELETE/orgs/{orgId}/projects/{projectId}/routes/{routeId}/targets/{targetId}Update / remove target
GET/orgs/{orgId}/domainsList org domains
GET/orgs/{orgId}/domains/{domainId}Get domain
MethodPathAuthDescription
GET/POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backupsList / create backup config
PATCH/DELETE/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}Update / delete config
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}/triggerTrigger now
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}/objectsList backup objects in storage
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/backups/{id}/restoreRestore from object
GET/PUT/DELETE/orgs/{orgId}/system-backupSystem backup config
POST/orgs/{orgId}/system-backup/triggerTrigger system backup
GET/orgs/{orgId}/system-backup/objectsList system backup objects
MethodPathAuthDescription
GET/POST/orgs/{orgId}/notification-channelsList / create channels
PUT/DELETE/orgs/{orgId}/notification-channels/{id}Update / delete channel
GET/PUT/DELETE/orgs/{orgId}/email-configOrg SMTP config
MethodPathAuthDescription
GET/orgs/{orgId}/git-integrationsList git integrations
POST/orgs/{orgId}/git-integrations/githubConnect GitHub App
POST/orgs/{orgId}/git-integrations/oauthConnect GitLab / Gitea via OAuth
GET/DELETE/orgs/{orgId}/git-integrations/{id}Get / delete integration
GET/orgs/{orgId}/git-integrations/{id}/reposList accessible repos
GET/orgs/{orgId}/git-integrations/{id}/branchesList branches for a repo
GET/POST/orgs/{orgId}/registry-integrationsList / add registry
DELETE/orgs/{orgId}/registry-integrations/{id}Remove registry
GET/POST/orgs/{orgId}/storage-integrationsList / add storage
DELETE/orgs/{orgId}/storage-integrations/{id}Remove storage
MethodPathAuthDescription
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/db/schemaLive schema
POST/orgs/{orgId}/projects/{projectId}/services/{serviceId}/db/queryExecute SQL query
MethodPathAuthDescription
GET/orgs/{orgId}/nodes/{nodeId}/terminalWebSocket — node shell
GET/orgs/{orgId}/projects/{projectId}/services/{serviceId}/pods/{podName}/terminalWebSocket — pod shell
POST/webhooks/github/{integrationId}HMACInbound GitHub push webhook
POST/webhooks/deploy/{serviceId}tokenInbound deploy webhook
GET/system/versionAPI version
GET/healthHealth check

GET /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)
HEADSCALE_URLNoHeadscale API URL
HEADSCALE_API_KEYNoHeadscale API key
GATEWAY_IPNoGateway mesh IP — seeds the gateway node on first boot
GATEWAY_HOSTNAMENoGateway hostname — used for gateway node seeding
HOST_GATEWAY_IPNoDocker bridge IP — used when API runs in Docker to reach gateway node_exporter
PUBLIC_IPNoGateway public IP — backfilled on the gateway node record
DOMAINNoRoot domain — seeds the domain record on first org
K3S_SERVER_URLNoK3s API URL (default: in-cluster)
KUBECONFIGNoPath to kubeconfig (for out-of-cluster dev)
BUILTIN_REGISTRY_ENDPOINTNoSeed a built-in registry row on org creation

Terminal window
cd apps/api
go run main.go

API at http://localhost:4000 · Docs at http://localhost:4000/docs

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