REST API¶
Doco-CD exposes a RESTful API at the /v1/api endpoint. Set both HTTP_TLS_CERT_FILE and HTTP_TLS_KEY_FILE if you want doco-cd itself to serve the API over HTTPS.
OpenAPI documentation¶
Set OPENAPI_ENABLED=true to expose the public OpenAPI 3.2 documentation. The feature is disabled by default.
| API surface | OpenAPI document | Swagger UI |
|---|---|---|
| REST and health | /openapi/rest.json | /docs/ |
| Webhooks | /openapi/webhooks.json | /docs/webhooks/ |
Authentication¶
Set the API_SECRET or API_SECRET_FILE environment variable in the container to enable the API, see App Settings.
Use the x-api-key header to authenticate requests to the API using the secret value.
Example:
Query Parameters¶
Management endpoints support these common query parameters:
| Query Parameter | Type | Description |
|---|---|---|
context | string | Docker context for project, stack, and scheduled-job endpoints (default: default). |
timeout | integer | Timeout in seconds (default: 30). |
Docker context selection¶
Project, stack, and scheduled-job endpoints accept one optional context query parameter. If it is omitted or set to default, the endpoint uses the default Docker context. Named contexts must exist in the Docker CLI context store available to doco-cd.
These endpoints return the selected external context name in the X-Doco-CD-Context response header. Their JSON response shapes do not change.
Endpoints¶
Health Check¶
Doco-CD exposes a health check endpoint at /v1/health that, if the application is healthy, returns a 200 status code and the following JSON response:
If the application is not healthy, the endpoint returns a 503 status code and the following JSON response:
Deployment Runs¶
The API tracks deployment-related runs (for example webhook-triggered deployments, API-triggered poll runs, and automatic, manual, or recovered scheduled-job executions) in memory. Use these endpoints to inspect the current status and recent history by job_id. Each run's deployments collection reports the resolved stack and Docker context targets. A single poll or webhook run can contain targets from multiple contexts. Automatic attempts suppressed by a deployment retry policy are skipped, not successful. The per-target reason distinguishes delay from budget_exhausted and suppression retains the failed desired release's state. Runs whose deployments are all paused are also skipped, with a pause reason. Mixed paused/active runs succeed if active work succeeds; any real failure takes precedence.
| Endpoint | Method | Description | Query Parameters |
|---|---|---|---|
/v1/api/runs | GET | List recent tracked deployment runs | - limit (integer, default: 50, max: 200)- status (string, optional): accepted, running, succeeded, failed, skipped- trigger (string, optional): webhook, poll, scheduled_job, mirror_compaction |
/v1/api/run/{jobID} | GET | Get details for a specific run/job ID |
History is bounded by the configured per-trigger limit (currently 50 runs in the application) and a seven-day TTL. All run timestamps use doco-cd's configured timezone (TZ), including the UTC offset for each timestamp. Accepted and running records are protected from eviction until they become terminal. History is not persisted across doco-cd restarts; retained one_off scheduled executions can be reconstructed during recovery.
Example Requests¶
curl --request GET \
--url 'https://cd.example.com/v1/api/runs?status=running&limit=20' \
--header 'x-api-key: your-api-key'
curl --request GET \
--url 'https://cd.example.com/v1/api/run/550e8400-e29b-41d4-a716-446655440000' \
--header 'x-api-key: your-api-key'
Polling¶
| Endpoint | Method | Description | Query Parameters |
|---|---|---|---|
/v1/api/poll/run | POST | Trigger a poll run for all polling targets in the body (JSON). | - wait (boolean, default: true): Wait for the poll run to finish before responding. |
The request body must be a JSON array of poll configurations, each containing at least a url field containing the Git clone URL to the repository.
The fields run_once, interval and schedule will be ignored for poll runs triggered via the API, as they are only relevant for the scheduled poll runs. They are still validated when the request is parsed: a configuration that sets both a schedule and a non-zero interval is rejected with 400 Bad Request, because the two are mutually exclusive.
Poll runs triggered via the API count as manual deployments for sync windows. If a sync window defers every deployment of the run, the run status is skipped and, with wait=true, the endpoint responds with 202 Accepted and a message like deployment of my-app deferred by sync window business-hours until 2026-01-05T08:00:00+01:00.
API-triggered polls also bypass the deployment's retry delay and exhausted-budget pause for that attempt only, including limit: 0. This applies to both synchronous (wait=true) and asynchronous (wait=false) runs. A failed manual deployment does not consume or refill the automatic retry budget but a complete success clears the failure and retry state. Source trust checks, sync windows, and other safety checks still apply. Automatic webhooks and redeploy reconciliation do not receive this bypass.
Manual polls do not bypass pause. Inline deployments accept "pause": true. With wait=true, a poll whose deployments are all paused returns 202 Accepted with the pause reason as content.
Example Request¶
Minimal example using the default settings:
curl --request POST \
--url 'https://cd.example.com/v1/api/poll/run?wait=true' \
--header 'content-type: application/json' \
--header 'x-api-key: your-api-key' \
--data '[
{
"url": "https://github.com/your/repo.git",
}
]'
Example with custom reference and inline deployment configuration:
curl --request POST \
--url 'https://cd.example.com/v1/api/poll/run?wait=true' \
--header 'content-type: application/json' \
--header 'x-api-key: your-api-key' \
--data '[
{
"url": "https://github.com/your/repo.git",
"reference": "dev",
"deployments": [
{
"name": "my-app",
"working_dir": "/app",
"env_files": [
".env"
]
}
]
}
]'
Scheduled Jobs¶
| Endpoint | Method | Description | Query Parameters |
|---|---|---|---|
/v1/api/jobs | GET | List all discovered scheduled jobs | - stack (string, optional): Return scheduled jobs only for one stack/project. |
/v1/api/job/{jobName}/run | POST | Trigger a configured scheduled job immediately. | - stack (string, optional): Limit matching to a specific stack/project.- wait (boolean, default: true): Wait for the triggered run to finish before responding. |
For Compose one_off jobs, status is running while an execution is active. Otherwise, if the source service container is inactive (created, exited, or dead), status shows the most recent recorded one-off exit status, such as exited (0) or exited (7), instead of an older exit status of the source container. An active source container keeps its own state. Exit results are kept in memory. If no result is available, status falls back to the source container's state. This occurs after a doco-cd restart without a recovered execution, or after a run that failed without an exit code, for example because the image pull failed. Compose restart jobs use the source container's state.
What is the jobName for a scheduled job?
jobName is the runtime name of the scheduled target:
- Docker (standalone): the container name (for example
my-stack-backup-1) - Docker Swarm: the service name (for example
my-stack_backup)
How do I find the jobName for a scheduled job?
You can get the jobName from the scheduler logs ("job":"...") or from GET /v1/api/jobs.
- If multiple jobs share the same
jobName, providestackto disambiguate for the run endpoint. - If the same job and stack names exist on multiple Docker contexts, provide
context. - If the matched job is disabled, the run endpoint returns a conflict response.
Common run endpoint outcomes
200 OK: run triggered and completed (wait=true).202 Accepted: run accepted and running in background (wait=false).404 Not Found: no scheduled job matchedjobName(and optionalstack).409 Conflict: matched job is disabled or the selection is ambiguous.
Example Request¶
Sync Windows¶
| Endpoint | Method | Description | Query Parameters |
|---|---|---|---|
/v1/api/sync-windows | GET | List the configured sync windows and, if a target is given, whether it may be deployed right now. | - repository (string, optional): Repository name to evaluate, e.g. github.com/acme/app.- deployment (string, optional): Deployment (Compose project or Swarm stack) name to evaluate.- context (string, optional): Docker context name to evaluate (default: default).- origin (string, optional): automatic (default) or manual (API/MCP poll runs). |
The response lists every window with its settings and current state (active, active_since, active_until and next_start). If at least one of repository, deployment or context is given, the response also contains a decision for that target:
| Field | Description |
|---|---|
allowed | Whether the target may be deployed right now. |
manual_override | true if the deployment is only allowed because it is manual and every blocking window has manual_sync. |
windows | Names of the windows that block (or, with manual_override, would block) the deployment. |
next_open | Earliest time the target may be deployed again, if it is blocked and the time is known. |
Example Request¶
curl --request GET \
--url 'https://cd.example.com/v1/api/sync-windows?repository=github.com/acme/app&deployment=web' \
--header 'x-api-key: your-api-key'
{
"content": {
"time": "2026-01-03T10:00:00+01:00",
"windows": [
{
"name": "weekend-freeze",
"kind": "deny",
"schedule": "0 18 * * 5",
"duration": "62h0m0s",
"timezone": "Europe/Berlin",
"repositories": [],
"deployments": [],
"contexts": [],
"manual_sync": false,
"active": true,
"active_since": "2026-01-02T18:00:00+01:00",
"active_until": "2026-01-05T08:00:00+01:00",
"next_start": "2026-01-09T18:00:00+01:00"
}
],
"decision": {
"allowed": false,
"manual_override": false,
"windows": ["weekend-freeze"],
"next_open": "2026-01-05T08:00:00+01:00"
}
},
"job_id": "550e8400-e29b-41d4-a716-446655440000"
}
Storage¶
| Endpoint | Method | Description | Query Parameters |
|---|---|---|---|
/v1/api/storage/compact | POST | Compact the Git mirrors in the data directory into a single packfile per mirror. | - repository (string, optional): Only compact the mirrors of this repository, e.g. github.com/acme/app or a clone URL.- mode (string, default: repack): repack re-encodes all objects, copy only consolidates the packfiles.- max_size (integer, default: 268435456): Size in bytes above which repack skips a mirror. 0 disables the limit.- wait (boolean, default: false): Wait for the compaction to finish before responding. |
The compaction is tracked as a run with the trigger mirror_compaction. With wait=true, the response contains the finished run. Its status is succeeded if at least one mirror was compacted and skipped if none was, and its message summarizes the result of every mirror.
Common outcomes
200 OK: compaction finished (wait=true).202 Accepted: compaction started in the background (wait=false). Use thejob_idwith/v1/api/run/{jobID}.400 Bad Request: unknownmodeor invalidmax_size.404 Not Found: no Git mirror exists, or none of the givenrepository.409 Conflict: another compaction is running. Thejob_idof the response is the one of that run.500 Internal Server Error: a mirror failed to compact (wait=true). The response contains the summary.503 Service Unavailable: doco-cd is shutting down and cancelled the compaction or did not start it.
Example Request¶
curl --request POST \
--url 'https://cd.example.com/v1/api/storage/compact?wait=true' \
--header 'x-api-key: your-api-key'
{
"content": {
"job_id": "550e8400-e29b-41d4-a716-446655440000",
"trigger": "mirror_compaction",
"status": "succeeded",
"message": "repack of 3 mirrors: 2 compacted, 1 skipped_busy; packfiles 22.2 MiB -> 12.1 MiB",
"created_at": "2026-01-03T10:00:00.120391+01:00",
"started_at": "2026-01-03T10:00:00.120584+01:00",
"finished_at": "2026-01-03T10:00:09.412873+01:00",
"updated_at": "2026-01-03T10:00:09.412873+01:00"
},
"job_id": "550e8400-e29b-41d4-a716-446655440000"
}
Compose Projects¶
Note
Project management endpoints are only available for compose projects, and will not work for Swarm stacks. To manage Swarm stacks, see the Swarm Stacks section below.
| Endpoint | Method | Description | Query Parameters |
|---|---|---|---|
/v1/api/projects | GET | List all deployed compose projects | - all (boolean, default: false): Return all projects including inactive ones. |
/v1/api/project/{projectName} | GET | Get details of a project | |
/v1/api/project/{projectName} | DELETE | Remove a project | - volumes (boolean, default: true): remove all associated volumes.- images (boolean, default: true): remove all associated images. |
/v1/api/project/{projectName}/start | POST | Start a project | |
/v1/api/project/{projectName}/stop | POST | Stop a project | |
/v1/api/project/{projectName}/restart | POST | Restart a project | |
/v1/api/project/{projectName}/recreate | POST | Force-recreate a project or service | - service (string, optional): Name of the service to recreate.- timeout (integer, default: 30): Time in seconds to wait for containers to stop. |
Managed recreation uses the revision recorded on the deployment, not the latest branch or tag. For legacy Git deployments from the same repository as their deploy config, it exports the deployed commit from the cached mirror if necessary and moves the recreated services to the artifact layout. There is no need to stop the project before recreating it.
Managed recreation returns 409 Conflict when the cached Git/OCI source cannot be verified or the deployed Git commit cannot be exported from the mirror. Run a normal deployment with force_recreate: true to refresh the source cache and deployment metadata. To see the error response in scripts, use curl --fail-with-body rather than curl --fail, and check its exit status before reporting success.
Swarm Stacks¶
Note
Stack management endpoints are only available if Doco-CD is running in a Docker Swarm environment.
| Endpoint | Method | Description | Query Parameters |
|---|---|---|---|
/v1/api/stacks | GET | List all deployed Swarm stacks | |
/v1/api/stack/{stackName} | GET | Get details of a Swarm stack | |
/v1/api/stack/{stackName} | DELETE | Remove a Swarm stack | |
/v1/api/stack/{stackName}/scale | POST | Rescale a Swarm stack or service | - replicas (integer): Scale to n replicas.- service (string, optional): Name of service to scale.- wait (boolean, default: true): Wait for service to be running/healthy |
/v1/api/stack/{stackName}/restart | POST | Restart/Redeploy a Swarm stack or service | - service (string, optional): Name of service to restart. |
/v1/api/stack/{stackName}/run | POST | Trigger one or all jobs in stack | - service (string, optional): Name of the job service to run. |
Example Request¶
List all deployed compose projects¶
Remove a specific project without removing associated volumes¶
curl -X DELETE -H "x-api-key: your_api_key" "http://example.com/v1/api/project/my_project?volumes=false"
Restart a specific project with a custom timeout¶
curl -X POST -H "x-api-key: your_api_key" "http://example.com/v1/api/project/my_project/restart?timeout=60"
Force-recreate all services in a Compose project¶
Force-recreate a specific Compose service¶
curl -X POST -H "x-api-key: your_api_key" "http://example.com/v1/api/project/my_project/recreate?service=web"