REST API v1
DeployMill's /api/v1 REST API is an HTTP mirror of the MCP tool surface, letting scripts, CI/CD pipelines, and other HTTP clients drive DeployMill without setting up a full MCP client. Every deploy-lifecycle tool an agent can call over MCP, including mutations like deploy, rollback, env-var changes, object storage, managed database, and backups, is also reachable here. /api/v1 additionally exposes two tools that have no MCP equivalent: create_app (over MCP you create apps through the higher-level start_project / import_repo workflows) and list_projects. A parity test (test/restApi.test.ts) enforces this mirror so a newly added MCP tool can't silently skip the REST surface.
/api/v1and API keys need a paid plan. Both the REST surface (restApiAccess) and org API keys (apiKeys) are part of the envelope a card lifts on, so a free Explore workspace calling/api/v1gets a codedupgrade_requiredcarrying an upgrade pointer. The agent deploy loop is never trapped by this: everything here is also callable overPOST /mcp, which stays open on every plan. See Upgrading.
Endpoints
| Endpoint | Notes |
|---|---|
GET /api/v1/openapi.json | OpenAPI 3.1 schema for the full v1 surface. No auth required. |
POST /api/v1/tools/:name | Call a tool by name. Auth required. |
GET /api/v1/notifications | The caller's own notification feed, newest first, plus their unread count. ?limit=N and ?before=<id> paginate. Auth required. |
POST /api/v1/notifications/read | Mark the caller's notifications read. Body { "deliveryIds": [...] }, or an empty body to mark them all. Auth required. |
GET /api/v1/notifications/prefs | The caller's own per-category × channel notification preferences. Auth required. |
PUT /api/v1/notifications/prefs | Update those preferences. Body { "prefs": [{ "category", "channel", "enabled" }] }. Audited. Auth required. |
The notification routes are the headless mirror of the dashboard's notification centre, scoped to the calling user in the calling org, so you only ever see and change your own.
Authentication
POST /api/v1/tools/:name accepts two kinds of bearer credential. Pass either as Authorization: Bearer <token>:
- OAuth 2.0 access token: the same token used for the
POST /mcpendpoint. Best for agents and interactive sessions. Obtain one by running the standard OAuth flow (Dynamic Client Registration + PKCE) against/api/auth/mcp/: - Org-scoped API key (
dm_<hex>): best for CI/CD pipelines and scripts. Created in the dashboard under Organization Settings → API Keys. An org owner must enable API-key access before keys can be created or used. Each key carries a fixed role (member or admin), and keys expire: a new key gets a 90-day lifetime by default, and you pick the window when you create it. An expired key is refused with401and the codeapi_key_expired, so mint a replacement and rotate.
Whichever credential you use, calls are scoped to the org it belongs to, and role-gated tools follow the caller's role (see below).
For local development, bearer local-dev works against a localhost server. Against a preview with E2E_BOOTSTRAP_SECRET set, use that value as the bearer.
Available tools
v1 exposes the full tool surface, grouped by area. The authoritative list (with input shapes and response formats) is always GET /api/v1/openapi.json. The table below is a convenience snapshot.
| Area | Tools |
|---|---|
| Account | get_account, list_templates, search_docs |
| Apps | list_apps, get_app, list_projects, list_deployments, get_logs, get_timeline, debug_app, create_app, deploy, cancel_deploy, rollback, stop_app, start_app, sleep_app, delete_app, set_app_protection, set_app_resources, set_app_sleep_policy |
| Object storage | list_objects, put_object, get_object, delete_object |
| Database | describe_database, query_database, swap_database |
| Backups | list_backups, create_backup, restore_backup, download_backup, verify_backup |
| Domains | list_domains, attach_domain, detach_domain |
| Env vars | list_env_vars, set_env_vars, delete_env_vars |
| Secrets | list_secrets, request_secret, secret_request_status, delete_secret, bind_secret, share_secret, unshare_secret |
| Previews | list_previews, create_preview, set_preview_ttl (redeploy a preview via deploy, and delete one via delete_app, addressing it by (parentApplicationId, ref)) |
| Source | list_files, get_file, push_files, get_clone_credentials |
| Workflows | start_project, import_repo, reconcile_project |
Read tools (list_*, get_*, search_docs) never return secret values: list_env_vars and list_secrets return key names only. Secret values are still entered through the request_secret browser hand-off, and the value never crosses the API client.
A few tools are capability-gated: writing to the secret vault (request_secret, bind_secret, delete_secret — list_secrets only needs secret.read, which members hold by default) and delete_app (including deleting a preview, which requires the preview.delete capability) default to admin (or owner). Gating is by the caller's effective permissions (their role's defaults adjusted by any per-member grants/revokes), not by rank alone, so a member granted secret.write may manage secrets and an admin who's had it revoked may not. A caller lacking the required capability gets a 403. get_account returns the caller's effective permissions array and their memberApps quota (min(org, member)), so an agent can check what it may do and how many apps it may run before acting. See the role matrix for the full breakdown.
Calling a tool
Send a POST with Content-Type: application/json and a JSON body matching the tool's input shape. An empty object {} works for tools with no required inputs.
# List all apps in the authenticated org (read)
curl -s https://your-deploymill.example.com/api/v1/tools/list_apps \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{}'
# Get an app's detail + live health (read)
curl -s https://your-deploymill.example.com/api/v1/tools/get_app \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"applicationId": "app_abc123"}'
# Trigger a deploy of an app's current branch (mutation)
curl -s https://your-deploymill.example.com/api/v1/tools/deploy \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"applicationId": "app_abc123"}'
Error responses
| Status | Meaning |
|---|---|
400 | Business-rule violation, e.g. a quota exceeded or an invalid state. The body's code is the machine-readable reason. |
401 | Missing or invalid bearer credential (OAuth token or dm_ API key) |
403 | The caller's org does not own the referenced resource, or the caller's role is too low for a role-gated tool |
404 | Tool name not found in the v1 registry |
422 | Input validation failed, and the detail field lists the Zod errors |
500 | Unexpected server error |
Mutations and the safety guardrails
Mutating over REST runs the same code path (and the same agent-safety guardrails) as the MCP surface: health-gated deploys with auto-rollback, isolated previews, the secret hand-off the client never sees, and dry-runnable reconcile_project plans. There's no behavioural difference between calling deploy over /api/v1 and over /mcp, so pick whichever transport fits your client.