API Reference
Everything in the DockPod panel is an HTTP call to DockPod's own REST API. The Terraform provider is built on it, and so is anything else that drives DockPod from the outside.
The specification is openapi.yaml — OpenAPI 3.1, which you can feed to Swagger UI, Redoc, Insomnia, Bruno, or a client generator.
# Browse it locally
npx @redocly/cli preview-docs https://guide.dockpod.io/openapi.yaml
# Or generate a client
npx @openapitools/openapi-generator-cli generate \
-i https://guide.dockpod.io/openapi.yaml -g python -o ./dockpod-clientAuthentication
Generate a key under Settings → API Keys, then send it as a bearer token:
curl -H "Authorization: Bearer dp_mcp_xxxxxxxxxxxxxxxxxxxx" \
https://panel.example.com/api/deploy/appsThe key is shown once, at creation — copy it then. It's the same key type MCP clients use.
The panel's own short-lived JWT is accepted on the same header, which is why one guard covers both. An API key is what any external caller should use: it doesn't expire on its own, and you can revoke it without affecting your login.
Saving is not applying
The one behaviour worth knowing before writing anything against this API:
Creating or editing a domain, a database link, or an app's environment variables saves the change. It does not deploy. The running containers keep their current configuration until a deploy applies it.
So this sequence leaves the app serving its old config:
APP=https://panel.example.com/api/deploy/apps/$APP_ID
AUTH="Authorization: Bearer $DOCKPOD_API_KEY"
curl -X POST -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"domain":"api.example.com","service_name":"app","container_port":8000,"redirect_www":true}' \
"$APP/domains"Read pending_changes on the app to see what is waiting:
curl -s -H "$AUTH" "$APP" | jq .pending_changes
# ["domains"]And apply it with a deploy:
curl -X POST -H "$AUTH" "$APP/deploy"A successful deploy clears the whole set. A failed one leaves it pending, because it still is. There's no way to apply one pending change without the others — every deploy reads env vars, domains and database links fresh.
The reasoning, and the measurements behind it, are in Saving vs. Applying.
Editing a domain in place
PATCH /api/deploy/apps/{id}/domains/{domainId} takes only the fields you mean to change; the rest keep their stored values. This is how you turn the www redirect on for a domain that already exists:
curl -X PATCH -H "$AUTH" -H 'Content-Type: application/json' \
-d '{"redirect_www":true}' \
"$APP/domains/$DOMAIN_ID"Note that the redirect_www flag belongs on the canonical domain — it's what creates the redirect router for the counterpart hostname. Adding the www. hostname as a second domain is rejected as a collision, which is the mistake this endpoint exists to spare you.
What is specified, and what isn't
openapi.yaml covers the surface infrastructure-as-code tooling needs:
| Area | Specified |
|---|---|
| Git Deployment apps — CRUD, deploy, start, stop | ✅ |
| Domains — list, add, edit, remove | ✅ |
| Database links — list, link, unlink | ✅ |
| Provisioned databases — CRUD, start, stop, backup | ✅ |
| Scheduled tasks — CRUD | ✅ |
| Containers, images, volumes, networks | ❌ not yet |
| Paste & Deploy / 1-Click | ❌ not yet |
| Git providers, settings, account, licensing | ❌ not yet |
The unspecified endpoints exist and work — they're what the panel's other pages call. They just aren't written up here yet. internal/server/routes.go is the complete list in the meantime.
Rate limits and errors
Only the unauthenticated endpoints are rate-limited (login, setup, 2FA verification). Authenticated calls are not.
Errors come back as {"error": "..."} with a conventional status code. Two are worth special handling:
409 Conflict— most often a hostname that's already taken, by another app's domain, a 1-Click deployment, the panel domain, the MCP domain, or as the www counterpart of a domain that already redirects.403 Forbiddenwith{"error":"plan_limit_reached","limit":N,"feature":"..."}— a Free-plan ceiling. See Pricing.