REST API Reference
Browse the complete OpenAPI specification in Swagger UI.
Base Configuration
- Base URL:
http://localhost:8080/api/v1 - Content-Type:
application/json - OpenAPI Version: 3.0.0
- Live OpenAPI Document:
GET /api/v1/openapi.json
Authentication
The API supports multiple authentication methods:
- API Keys: Include
Authorization: Bearer dagu_<key>header (requires Builtin Auth) - Webhooks: Trigger endpoints use per-DAG webhook credentials. Send
Authorization: Bearer dagu_wh_<token>(requires Builtin Auth) - JWT Token: Include
Authorization: Bearer <jwt-token>header (from login endpoint) - Basic Auth: Include
Authorization: Basic <base64(username:password)>header - No Authentication: When auth is disabled (default for local development)
API keys are recommended for general programmatic access as they support role-based permissions. See API Keys for details. For triggering specific DAGs from external systems, see Webhooks.
System Endpoints
Health Check
Endpoint: GET /api/v1/health
Checks the health status of the Dagu server.
Response (200):
{
"status": "healthy",
"version": "1.14.5",
"uptime": 86400,
"timestamp": "2024-02-11T16:30:45.123456789Z"
}Response when unhealthy (503):
{
"status": "unhealthy",
"version": "1.14.5",
"uptime": 300,
"timestamp": "2024-02-11T16:30:45.123456789Z",
"error": "Scheduler not responding"
}Response Fields:
status: Server health status ("healthy" or "unhealthy")version: Current server versionuptime: Server uptime in secondstimestamp: Current server time
Get Scheduler Pause State
Endpoint: GET /api/v1/services/scheduler/pause
Reports whether scheduler-driven run creation is paused. Readable by any authenticated user.
Response (200):
{
"paused": true,
"pausedAt": "2024-02-11T16:30:45Z",
"pausedBy": "admin",
"reason": "database migration"
}Response Fields:
paused: Whether the scheduler is currently pausedpausedAt: RFC3339 timestamp of the pause, omitted when runningpausedBy: User who paused the scheduler, omitted when runningreason: Operator-supplied explanation, omitted when running or not given
Pause or Resume the Scheduler
Endpoint: POST /api/v1/services/scheduler/pause
Pauses or resumes scheduled run creation for every DAG at once. Admin only.
Request Body:
{
"paused": true,
"reason": "database migration"
}paused(required): Target statereason(optional): Shown in the web UI banner while paused, up to 512 characters
Response (200): Success
Manual, webhook, and sub-DAG runs keep working while paused. Queued scheduled runs are aborted and catchup windows are discarded rather than deferred. See Pausing the Scheduler for the full behavior.
OpenAPI Document
Endpoint: GET /api/v1/openapi.json
Returns the normalized OpenAPI document served by this Dagu instance.
Notes:
- When authentication is enabled, this endpoint is authenticated like the rest of the API.
- The response advertises the mounted API path in
servers[0].urlsuch as/api/v1,/dagu/api/v1, or/dagu/rest. - The document includes the
/openapi.jsonpath itself, so clients can discover it from the schema.
DAG Management Endpoints
List DAGs
Endpoint: GET /api/v1/dags
Retrieves DAG definitions with optional filtering by name and labels.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| page | integer | Page number (1-based) | 1 |
| perPage | integer | Items per page (max 1000) | 50 |
| name | string | Filter DAGs by name | - |
| labels | string | Filter by labels. Comma-separated. Syntax: key, key=value, !key, key*, key=value*. Supports * and ? wildcards. AND logic. | - |
| sort | string | Sort field: name, nextRun, status, or lastRun | ui.dags.sort_field (fallback name) |
| order | string | Sort order: asc or desc | ui.dags.sort_order (fallback asc) |
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"dags": [
{
"fileName": "example",
"dag": {
"name": "example_dag",
"group": "default",
"schedule": [{"expression": "0 * * * *"}],
"description": "Example DAG",
"params": ["param1", "param2"],
"defaultParams": "{}",
"labels": ["example", "demo"]
},
"nextRun": "2026-03-29T09:30:00+09:00",
"latestDAGRun": {
"dagRunId": "20240101_120000",
"name": "example_dag",
"status": 1,
"statusLabel": "running",
"startedAt": "2024-01-01T12:00:00Z",
"finishedAt": "",
"log": "/logs/example_dag.log"
},
"suspended": false,
"errors": []
}
],
"errors": [],
"pagination": {
"totalRecords": 45,
"currentPage": 1,
"totalPages": 5,
"nextPage": 2,
"prevPage": null
}
}schedule entries are recurring cron objects such as {"expression":"0 * * * *"} or typed one-off start entries such as {"kind":"at","at":"2026-03-29T09:30:00+09:00"}. nextRun is the scheduler-aware next planned run time for that DAG. For a pending one-off start entry, nextRun can remain visible even after that timestamp has passed, until the scheduler consumes it.
Schedule aliases in authored YAML are returned as canonical cron expressions. For example, "@daily" becomes "0 0 * * *" in schedule metadata.
sort=status orders DAGs by the status of their latest run: running, waiting, queued, failed, rejected, aborted, partially succeeded, then succeeded. sort=lastRun orders them by the start time of their latest run. Both sorts read every matching DAG, so the order holds across pages. DAGs without a run come last in either order, sorted by name. Above 300 matching DAGs, the list is sorted by name instead and errors says why. Pinned DAGs are listed first; the sort applies within the pinned and unpinned parts.
Create DAG
Endpoint: POST /api/v1/dags
Creates a new DAG file with the specified name. Optionally initializes it with a provided YAML specification.
Request Body:
{
"name": "my-new-dag",
"spec": "steps:\n - run: echo hello" // Optional - YAML spec to initialize the DAG
}Notes:
- If
specis provided, it will be validated before creation - If validation fails, returns 400 with error details
- Without
spec, creates a minimal DAG with a single echo step
Response (201):
{
"name": "my-new-dag"
}Error Response (400):
{
"code": "bad_request",
"message": "Invalid DAG name format"
}Error Response (409):
{
"code": "already_exists",
"message": "DAG with this name already exists"
}Get DAG Details
Endpoint: GET /api/v1/dags/{fileName}
Fetches detailed information about a specific DAG.
Path Parameters:
| Parameter | Type | Description | Pattern |
|---|---|---|---|
| fileName | string | DAG file name | ^[a-zA-Z0-9_-]+$ |
Response (200):
{
"dag": {
"name": "data_processing_pipeline",
"group": "ETL",
"schedule": [
{"expression": "0 2 * * *"},
{"expression": "0 14 * * *"}
],
"description": "Daily data processing pipeline for warehouse ETL",
"env": [
"DATA_SOURCE=postgres://prod-db:5432/analytics",
"WAREHOUSE_URL=${env.WAREHOUSE_URL}"
],
"log_dir": "/var/log/dagu/pipelines",
"handler_on": {
"success": {
"name": "notify_success",
"command": "notify.sh 'Pipeline completed successfully'"
},
"failure": {
"name": "alert_on_failure",
"command": "alert.sh 'Pipeline failed' high"
},
"exit": {
"name": "cleanup",
"command": "cleanup_temp_files.sh"
}
},
"steps": [
{
"name": "extract_data",
"id": "extract",
"description": "Extract data from source database",
"dir": "/app/etl",
"command": "python",
"args": ["extract.py", "--date", "${params.date}"],
"stdout": "/logs/extract.out",
"stderr": "/logs/extract.err",
"output": "EXTRACTED_FILE",
"preconditions": [
{
"condition": "test -f /data/ready.flag"
}
]
},
{
"name": "transform_data",
"id": "transform",
"description": "Apply transformations to extracted data",
"command": "python transform.py --input=${env.EXTRACTED_FILE}",
"depends": ["extract_data"],
"output": "TRANSFORMED_FILE",
"repeat_policy": {
"repeat": false,
"interval": 0
},
"mail_on_error": true
},
{
"name": "load_to_warehouse",
"id": "load",
"run": "warehouse-loader",
"params": "{\"file\": \"${env.TRANSFORMED_FILE}\", \"table\": \"fact_sales\"}",
"depends": ["transform_data"]
}
],
"delay": 30,
"hist_retention_days": 30,
"preconditions": [
{
"eval": "$(date +%u)",
"expected": "re:[1-5]"
}
],
"max_active_runs": 1,
"max_active_steps": 5,
"params": ["date", "env", "batch_size"],
"paramDefs": [
{
"name": "date",
"type": "string",
"description": "Pipeline run date",
"pattern": "^\\d{4}-\\d{2}-\\d{2}$"
},
{
"name": "env",
"type": "string",
"default": "dev",
"enum": ["dev", "staging", "prod"]
},
{
"name": "batch_size",
"type": "integer",
"default": 1000,
"minimum": 1,
"maximum": 10000
}
],
"paramSchema": {
"type": "object",
"required": ["env"],
"properties": {
"env": {
"type": "string",
"enum": ["dev", "staging", "prod"]
},
"batch_size": {
"type": "integer",
"default": 1000,
"minimum": 1,
"maximum": 10000
}
}
},
"defaultParams": "env=\"dev\" batch_size=\"1000\"",
"labels": ["production", "etl", "daily"]
},
"localDags": [
{
"name": "warehouse-loader",
"dag": {
"name": "warehouse_loader_subdag",
"steps": [
{
"name": "validate_schema",
"command": "validate_schema.py"
},
{
"name": "load_data",
"command": "load_to_warehouse.py",
"depends": ["validate_schema"]
}
]
},
"errors": []
}
],
"latestDAGRun": {
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_140000_abc123",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_140000_abc123",
"name": "data_processing_pipeline",
"status": 4,
"statusLabel": "succeeded",
"queuedAt": "",
"startedAt": "2024-02-11T14:00:00Z",
"finishedAt": "2024-02-11T14:45:30Z",
"log": "/logs/data_processing_pipeline/20240211_140000_abc123.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}"
},
"suspended": false,
"errors": []
}paramSchema is present when Dagu can safely render a schema-backed object form directly in the start/enqueue UI. Clients should prefer paramSchema over paramDefs when both are present.
paramDefs is additive metadata derived from DAG-level params:. Typed clients can use it to render scalar controls when paramSchema is absent. paramDefs can be derived from inline rich params, top-level inline JSON Schema, and representable external schemas.
The DAG object also exposes histRetentionDays for DAG history retention.
defaultParams is the resolved shell-style default parameter string, not JSON. For example: environment="staging" batch_size="25".
When an inline rich param uses eval, paramDefs.default remains only the authored literal default, if one exists. Clients should not evaluate template defaults locally.
The DAG object also carries upcomingRuns: the next ten fire times of its start schedules, earliest first. Each entry has time, the scheduleIndex of its schedule entry, and the timezone it runs in, plus businessDay, extraBusinessDay, and skippedHolidays when a business calendar placed it. The list comes from the definition alone; upcomingRunsHold is suspended or schedulerPaused when none of the runs will start. GET /dags/{fileName}/spec and POST /dags/validate return the same fields.
Delete DAG
Endpoint: DELETE /api/v1/dags/{fileName}
Permanently removes a DAG definition from the system.
Response (204): No content
Error Response (404):
{
"code": "not_found",
"message": "DAG not found"
}Error Response (403):
{
"code": "forbidden",
"message": "Permission denied to delete DAGs"
}Get DAG Specification
Endpoint: GET /api/v1/dags/{fileName}/spec
Fetches the YAML specification of a DAG.
Response (200):
{
"dag": {
"name": "example_dag"
},
"spec": "name: example_dag\nsteps:\n - name: hello\n run: echo Hello",
"sha256": "4f2c0e7b9d1a...",
"errors": []
}sha256 is the lowercase hex SHA-256 of the file bytes in spec.
Update DAG Specification
Endpoint: PUT /api/v1/dags/{fileName}/spec
Updates the YAML specification of a DAG.
Request Body:
{
"spec": "name: example_dag\nsteps:\n - name: hello\n run: echo Hello World",
"expectedSha256": "4f2c0e7b9d1a..."
}expectedSha256 is optional. Set it to the sha256 of the spec that was edited. When the file has changed since, the server writes nothing and returns 409.
Response (200):
{
"errors": [],
"sha256": "9b8e61d0c3f4..."
}sha256 is the SHA-256 of the bytes written.
Response with Validation Errors (200):
{
"errors": [
"Line 5: Invalid step configuration - missing command",
"Line 10: Circular dependency detected between step1 and step2"
]
}Error Response (403):
{
"code": "forbidden",
"message": "Permission denied to edit DAGs"
}Error Response (409) - The current file does not match expectedSha256:
{
"code": "conflict",
"message": "DAG example_dag changed since it was read: expected sha256 4f2c0e7b9d1a..., current sha256 7a1d93e05b2c..."
}Validate DAG Specification
Endpoint: POST /api/v1/dags/validate
Validates a DAG YAML specification without persisting any changes. Returns a list of validation errors. When the spec can be partially parsed, the response may include parsed DAG details built with error-tolerant loading.
Request Body:
{
"spec": "steps:\n - name: step1\n run: echo hello\n - name: step2\n run: echo world\n depends: step1",
"name": "optional-dag-name" // Optional - name to use when spec omits a name
}Response (200):
{
"valid": true,
"errors": [],
"dag": {
"name": "example-dag",
"group": "default",
"description": "Validated DAG",
"schedule": [],
"params": [],
"defaultParams": "{}",
"labels": []
}
}Response with errors (200):
{
"valid": false,
"errors": [
"Step 'step2' depends on non-existent step 'missing_step'",
"Invalid cron expression in schedule: '* * * *'"
],
"dag": {
"name": "example-dag",
// Partial DAG details when possible
}
}Notes:
- Always returns 200 status - check
validfield to determine if spec is valid errorsarray contains human-readable validation messagesdagfield may contain partial DAG details even when validation fails- Use this endpoint to validate DAG specs before creating or updating DAGs
warningslists non-fatal spec warnings; they do not affectvalidissueslists the same errors, warnings, and notices as objects withseverity(error,warning, ornotice),source(load,build,warning, orvalueReference), andmessage. An issue also hasstepName,stepPath(such assteps[3]orhandler_on.failure),fieldPath(such assteps[3].with.url),line, andcolumnwhen knownvalueReferenceNoticeslists value-reference notices produced while loading the spec. They are not persistedreferenceScopelists what a step can reference:params,consts,envandsecretsnames (without their values), andstepswith the outputs other steps can use. Each entry has its canonicalreferencetoken, such as${params.region}or${steps.fetch.outputs.body}. A step'soutputsKnownisstaticwhen the spec gives its outputs andruntimewhen only a run does
Rename DAG
Endpoint: POST /api/v1/dags/{fileName}/rename
Changes the file ID of the DAG definition.
Request Body:
{
"newFileName": "new-dag-name"
}Response (200): Success
Error Response (400):
{
"code": "bad_request",
"message": "Invalid new file name format"
}Error Response (409):
{
"code": "already_exists",
"message": "A DAG with the new name already exists"
}DAG Execution Endpoints
Start DAG
Endpoint: POST /api/v1/dags/{fileName}/start
Creates and starts a DAG run with optional parameters.
Request Body:
{
"params": "{\"env\": \"production\", \"version\": \"1.2.3\"}",
"dagRunId": "custom-run-id",
"dagName": "on-demand-data-load",
"singleton": false,
"noReuse": false
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| params | string | JSON string of parameters. Values are validated and coerced against DAG param definitions or external parameter schema before execution. For named params, prefer a JSON object payload. JSON arrays are mainly for positional or mixed raw input. | No |
| dagRunId | string | Custom run ID | No |
| dagName | string | Override the DAG name used for this run (must satisfy DAG name validation) | No |
| singleton | boolean | If true, prevent starting if DAG is already running (returns 409) | No |
| noReuse | boolean | If true, recompute eligible build steps instead of reusing prior materializations | No |
| steps | string[] | Run only these steps, by step name or ID. Every other step is recorded as skipped and does not run. Not supported for agent DAGs | No |
| outputsFromRunId | string | Finished DAG-run of the same DAG whose work directory, and the outputs of the skipped steps that the selected steps depend on, are carried into the run. Only steps that succeeded there contribute outputs. Requires steps | No |
| outputsFromRuns | object | The run each skipped step takes its outputs from, keyed by step name or ID. Wins over outputsFromRunId for that step; the work directory still comes only from outputsFromRunId. Requires steps | No |
| outputs | object | Outputs of skipped steps, keyed by step name or ID and then by output name, with string values. Wins over outputsFromRunId and outputsFromRuns. Requires steps | No |
| outputsSources | object | The run each step of outputs was captured from, keyed by step name or ID, as {"runId": "...", "capturedAt": "<RFC 3339>"}; capturedAt is optional. Requires steps | No |
| ephemeral | boolean | If true, the run leaves no trace. See Ephemeral runs. Requires steps | No |
Tip: Set
stepsto try one step against the current definition without re-running the steps before it. The run is recorded like any other under the DAG's name. Only a skipped step that a selected step depends on, directly or through other steps, carries outputs. They come fromoutputsFromRunId,outputsFromRuns, oroutputs; without them, references to those outputs stay unresolved. Retrying a failed selected-steps run follows normal retry rules, so steps downstream of the failed one run too.
The success, failure, abort, wait, and exit handlers of a selected-steps run run only when a selected step is a graph end step, one that no other step depends on. handler_on.init runs as usual. Retries of the run keep the same rule.
An outputsFromRuns entry must name a skipped step and a finished run of the same DAG in which that step succeeded. Otherwise the request returns 400 with the message step X cannot be reused from run Y: <reason>. The run details of the new run list the source of each carried step in reusedInputs.
For example, to run fetch with a token its upstream login step would normally publish:
{
"steps": ["fetch"],
"outputs": {"login": {"token": "abc123"}}
}An outputs entry must name a skipped step. The name must be declared by the step's outputs, unless the step declares none, and a type: json output needs valid JSON. Naming the step's output: VAR sets that variable instead. Invalid entries return 400. The audit log records output names but not values.
outputsSources names the run each step's outputs came from, and the new run records it in reusedInputs. A step of outputs without an entry has no entry there. An entry for a step that is not in outputs returns 400. The named run does not need to exist.
Tip: Overriding the DAG name changes the identifier used for queue grouping, which is useful for ad-hoc executions that should not collide with scheduled runs.
Note:
GET /api/v1/dags/{fileName}returnsparamDefsfor typed UI rendering. The start endpoint still accepts a JSON string inparams, and the server performs the authoritative validation/coercion. For named params, clients should send a JSON object payload. Response (200):
{
"dagRunId": "20240101_120000_abc123"
}Response (409) - When singleton: true and DAG is already running:
{
"code": "already_running",
"message": "DAG example_dag is already running, cannot start in singleton mode"
}Ephemeral runs
With ephemeral: true, the selected steps run in a run that leaves no trace. ephemeral: true without steps returns 400.
- The run executes like any run, and its run details, step logs, step outputs, spec, and SSE stream are readable by its ID while it exists.
- It is not listed in run lists, queues, the dashboard, artifacts, or the events feed. Get DAG Run History includes it only with
includeEphemeral=true. It does not count toward history retention. - It produces no events, so notification rules and incident policies do not see it.
- Retry, reschedule, and edit-retry requests for it return
409. - The server removes it 10 minutes after it ends, and removes every ended ephemeral run when the server starts. A run that has not ended is never removed.
- The audit log records the start request, marked
ephemeral. - Its run details and run summaries carry
ephemeral: true.
An ephemeral run can be the outputsFromRunId or outputsFromRuns source of another request while it exists.
Start DAG (Synchronous)
Endpoint: POST /api/v1/dags/{fileName}/start-sync
Deprecated
Use POST /api/v1/dags/{fileName}/start for new integrations, then monitor the DAG run through the DAG-run status API or SSE. This endpoint remains available for backward compatibility.
Creates and starts a DAG run, waits for it to complete (or timeout), and returns the full execution details.
Request Body:
{
"timeout": 300,
"params": "{\"env\": \"production\"}",
"dagRunId": "custom-run-id",
"dagName": "sync-execution",
"singleton": false,
"noReuse": false
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| timeout | integer | Maximum seconds to wait for completion (1-86400) | Yes |
| params | string | JSON string of parameters. Values are validated and coerced against DAG param definitions or external parameter schema before execution. | No |
| dagRunId | string | Custom run ID | No |
| dagName | string | Override the DAG name used for this run | No |
| singleton | boolean | If true, prevent starting if DAG is already running (returns 409) | No |
| noReuse | boolean | If true, recompute eligible build steps instead of reusing prior materializations | No |
Response (200) - DAG completed or reached waiting status:
{
"dagRun": {
"rootDAGRunName": "my-dag",
"rootDAGRunId": "20240101_120000_abc123",
"dagRunId": "20240101_120000_abc123",
"name": "my-dag",
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-01-01T12:00:00Z",
"finishedAt": "2024-01-01T12:00:05Z",
"log": "/logs/my-dag.log",
"params": "{}",
"nodes": [
{
"step": {"name": "step1", "command": "echo hello"},
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-01-01T12:00:01Z",
"finishedAt": "2024-01-01T12:00:02Z"
}
]
}
}Response (408) - Timeout waiting for completion (DAG continues in background):
{
"code": "timeout",
"message": "timeout waiting for DAG my-dag to complete after 300 seconds; DAG run continues in background",
"dagRunId": "20240101_120000_abc123"
}Note: When a timeout occurs, the DAG run continues executing in the background. The response includes the
dagRunIdso you can monitor or cancel the run using other API endpoints.
Response (409) - When singleton: true and DAG is already running:
{
"code": "already_exists",
"message": "DAG my-dag is already running (singleton mode)"
}Note: If the DAG reaches a "waiting" status (e.g., a human-in-the-loop step requires approval), the endpoint returns immediately with 200 and the current status instead of blocking until timeout.
Enqueue DAG
Endpoint: POST /api/v1/dags/{fileName}/enqueue
Adds a DAG run to the queue for later execution.
Request Body:
{
"params": "{\"key\": \"value\"}",
"dagRunId": "optional-custom-id",
"dagName": "queued-ad-hoc-run",
"queue": "optional-queue-override",
"noReuse": false
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| params | string | JSON string of parameters. Values are validated and coerced against DAG param definitions or external parameter schema before the run is queued. | No |
| dagRunId | string | Custom run ID | No |
| dagName | string | Override the DAG name used for the queued run (must satisfy DAG name validation) | No |
| queue | string | Queue name override | No |
| noReuse | boolean | If true, recompute eligible build steps when the queued run starts | No |
Tip: When you override the DAG name, the queued run is tracked under the new identifier for both queue management and history records.
Response (200):
{
"dagRunId": "20240101_120000_abc123"
}Toggle DAG Suspension
Endpoint: POST /api/v1/dags/{fileName}/suspend
Controls whether the scheduler creates runs from this DAG.
Request Body:
{
"suspend": true
}Response (200): Success
Error Response (404):
{
"code": "not_found",
"message": "DAG not found"
}DAG Run History Endpoints
Get DAG Run History
Endpoint: GET /api/v1/dags/{fileName}/dag-runs
Fetches execution history of a DAG.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| includeEphemeral | boolean | Also return the ephemeral runs of this DAG, merged by start time under the same limit. Each carries ephemeral: true. No other list returns them | false |
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"dagRuns": [
{
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_140000_abc123",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_140000_abc123",
"name": "data_processing_pipeline",
"status": 4,
"statusLabel": "succeeded",
"queuedAt": "",
"startedAt": "2024-02-11T14:00:00Z",
"finishedAt": "2024-02-11T14:45:30Z",
"log": "/logs/data_processing_pipeline/20240211_140000_abc123.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}"
},
{
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_020000_def456",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_020000_def456",
"name": "data_processing_pipeline",
"status": 2,
"statusLabel": "failed",
"queuedAt": "",
"startedAt": "2024-02-11T02:00:00Z",
"finishedAt": "2024-02-11T02:15:45Z",
"log": "/logs/data_processing_pipeline/20240211_020000_def456.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}"
},
{
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240210_140000_ghi789",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240210_140000_ghi789",
"name": "data_processing_pipeline",
"status": 4,
"statusLabel": "succeeded",
"queuedAt": "",
"startedAt": "2024-02-10T14:00:00Z",
"finishedAt": "2024-02-10T14:42:15Z",
"log": "/logs/data_processing_pipeline/20240210_140000_ghi789.log",
"params": "{\"date\": \"2024-02-10\", \"env\": \"production\", \"batch_size\": 5000}"
}
],
"gridData": [
{
"name": "extract_data",
"history": [4, 2, 4]
},
{
"name": "transform_data",
"history": [4, 2, 4]
},
{
"name": "load_to_warehouse",
"history": [4, 0, 4]
}
]
}Get Specific DAG Run
Endpoint: GET /api/v1/dags/{fileName}/dag-runs/{dagRunId}
Gets detailed status of a specific DAG run.
Response (200):
{
"dagRun": {
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_140000_abc123",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_140000_abc123",
"name": "data_processing_pipeline",
"status": 4,
"statusLabel": "succeeded",
"queuedAt": "",
"startedAt": "2024-02-11T14:00:00Z",
"finishedAt": "2024-02-11T14:45:30Z",
"log": "/logs/data_processing_pipeline/20240211_140000_abc123.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}",
"nodes": [
{
"step": {
"name": "extract_data",
"id": "extract",
"command": "python",
"args": ["extract.py", "--date", "2024-02-11"]
},
"stdout": "/logs/data_processing_pipeline/20240211_140000_abc123/extract_data.stdout",
"stderr": "/logs/data_processing_pipeline/20240211_140000_abc123/extract_data.stderr",
"startedAt": "2024-02-11T14:00:30Z",
"finishedAt": "2024-02-11T14:15:45Z",
"status": 4,
"statusLabel": "succeeded",
"retryCount": 0,
"doneCount": 1,
"subRuns": [],
"subRunsRepeated": [],
"error": ""
},
{
"step": {
"name": "transform_data",
"id": "transform",
"command": "python transform.py --input=/tmp/extracted_20240211.csv",
"depends": ["extract_data"]
},
"stdout": "/logs/data_processing_pipeline/20240211_140000_abc123/transform_data.stdout",
"stderr": "/logs/data_processing_pipeline/20240211_140000_abc123/transform_data.stderr",
"startedAt": "2024-02-11T14:15:45Z",
"finishedAt": "2024-02-11T14:30:20Z",
"status": 4,
"statusLabel": "succeeded",
"retryCount": 0,
"doneCount": 1,
"subRuns": [],
"subRunsRepeated": [],
"error": ""
},
{
"step": {
"name": "load_to_warehouse",
"id": "load",
"run": "warehouse-loader",
"params": "{\"file\": \"/tmp/transformed_20240211.csv\", \"table\": \"fact_sales\"}",
"depends": ["transform_data"]
},
"stdout": "/logs/data_processing_pipeline/20240211_140000_abc123/load_to_warehouse.stdout",
"stderr": "/logs/data_processing_pipeline/20240211_140000_abc123/load_to_warehouse.stderr",
"startedAt": "2024-02-11T14:30:20Z",
"finishedAt": "2024-02-11T14:45:30Z",
"status": 4,
"statusLabel": "succeeded",
"retryCount": 0,
"doneCount": 1,
"subRuns": [
{
"dagRunId": "sub_20240211_143020_xyz456",
"params": "{\"file\": \"/tmp/transformed_20240211.csv\", \"table\": \"fact_sales\"}"
}
],
"subRunsRepeated": [],
"error": ""
}
]
}
}DAG Run Management Endpoints
List All DAG Runs
Endpoint: GET /api/v1/dag-runs
Retrieves the newest DAG runs with forward-only cursor pagination.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| name | string | Filter by DAG name | - |
| status | integer | Filter by status (0-8) | - |
| fromDate | integer | Unix timestamp start | - |
| toDate | integer | Unix timestamp end | - |
| dagRunId | string | Filter by run ID | - |
| limit | integer | Page size (max 500) | 100 |
| cursor | string | Opaque cursor returned by the previous page | - |
| labels | string | Filter by DAG labels. Comma-separated. Same syntax as DAG filtering. Supports * and ? wildcards. AND logic. | - |
| remoteNode | string | Remote node name | "local" |
The response includes nextCursor when older results are available. Repeat the same request with cursor=<nextCursor> to continue.
Status Values:
- 0: Not started
- 1: Running
- 2: Failed
- 3: Aborted
- 4: Success
- 5: Queued
- 6: Partial Success
- 7: Waiting
- 8: Rejected
DAG-run Runtime Conditions
DAGRunSummary and DAGRunDetails may include a conditions array when the DAG run status is queued. The current implementation emits queued runtime conditions only while the run remains queued. Conditions are omitted for non-queued statuses, and invalid checkedAt values are skipped when the API response is built.
Each condition object has:
| Field | Type | Description |
|---|---|---|
| type | string | Condition type. The current queued condition type is Queued. |
| status | string | True, False, or Unknown by schema. The scheduler currently writes True for queued conditions. |
| reason | string | Machine-readable reason. |
| message | string | Human-readable explanation. |
| checkedAt | string | RFC 3339 timestamp when the scheduler observed the condition. |
Current queued condition reasons:
| Reason | Meaning |
|---|---|
QueueConcurrencyLimitReached | The queue's active-run concurrency limit currently has no free slot. |
DispatchAdmissionPending | A distributed dispatch reservation already exists for the run. |
WorkerSelectorNotMatched | No healthy worker matches the run's required worker_selector. |
NoAvailableWorker | No healthy distributed worker is available. |
DispatchUnavailable | Distributed dispatch is temporarily unavailable and the error was not classified as a worker selector mismatch or no available worker. |
Response (200):
{
"dagRuns": [
{
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_160000_current",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_160000_current",
"name": "data_processing_pipeline",
"status": 1,
"statusLabel": "running",
"queuedAt": "",
"startedAt": "2024-02-11T16:00:00Z",
"finishedAt": "",
"log": "/logs/data_processing_pipeline/20240211_160000_current.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}"
},
{
"rootDAGRunName": "database_backup",
"rootDAGRunId": "20240211_150000_backup",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_150000_backup",
"name": "database_backup",
"status": 4,
"statusLabel": "succeeded",
"queuedAt": "",
"startedAt": "2024-02-11T15:00:00Z",
"finishedAt": "2024-02-11T15:45:30Z",
"log": "/logs/database_backup/20240211_150000_backup.log",
"params": "{\"target_db\": \"production\", \"retention_days\": 30}"
},
{
"rootDAGRunName": "ml_training_pipeline",
"rootDAGRunId": "20240211_143000_ml",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_143000_ml",
"name": "ml_training_pipeline",
"status": 5,
"statusLabel": "queued",
"queuedAt": "2024-02-11T14:30:00Z",
"startedAt": "",
"finishedAt": "",
"log": "/logs/ml_training_pipeline/20240211_143000_ml.log",
"conditions": [
{
"type": "Queued",
"status": "True",
"reason": "WorkerSelectorNotMatched",
"message": "No healthy worker matches the required worker selector; DAG-run is waiting in the queue.",
"checkedAt": "2024-02-11T14:31:00Z"
}
],
"params": "{\"model\": \"recommendation_v2\", \"dataset\": \"user_interactions\"}"
}
],
"nextCursor": "eyJvY2N1cnJlZEF0IjoiMjAyNC0wMi0xMVQxNDozMDowMFoiLCJkYWdSdW5JZCI6IjIwMjQwMjExXzE0MzAwMF9tbCJ9"
}Execute DAG Run from Inline Spec
Endpoint: POST /api/v1/dag-runs
Creates and starts a DAG-run directly from an inline YAML specification without writing a DAG file to disk.
Inline specifications have no source directory, so they cannot supply file dependencies for distributed execution. Save the workflow as a DAG file and start the saved DAG when a worker needs files stored beside it.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Target remote node for execution | "local" |
Request Body:
{
"spec": "steps:\n - name: extract\n run: ./extract.sh",
"name": "ad-hoc-extract",
"params": "{\"env\":\"prod\"}",
"dagRunId": "20241101_010203_custom",
"singleton": true,
"noReuse": false
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| spec | string | DAG specification in YAML format | Yes |
| name | string | Override DAG name used for validation and execution | No |
| params | string | JSON string persisted with the DAG-run and exposed to steps | No |
| dagRunId | string | Explicit run identifier. If omitted, one is generated | No |
| singleton | boolean | When true, aborts with 409 if the DAG already has active or queued runs | No |
| noReuse | boolean | If true, recompute eligible build steps instead of reusing prior materializations | No |
| steps | string[] | Run only these steps, by step name or ID; every other step is recorded as skipped. See Start DAG | No |
| outputsFromRunId | string | Finished DAG-run of the same DAG whose step outputs feed the skipped steps. Requires steps | No |
| outputsFromRuns | object | The run each skipped step takes its outputs from, keyed by step. Requires steps | No |
| outputs | object | Outputs of skipped steps, keyed by step and then output name. Requires steps | No |
| outputsSources | object | The run each step of outputs was captured from. Requires steps | No |
| ephemeral | boolean | Run the selected steps in a run that leaves no trace. See Ephemeral runs. Requires steps | No |
Response (200):
{
"dagRunId": "20241101_010203_custom"
}Error Response (400) - Invalid spec or missing spec field:
{
"code": "bad_request",
"message": "spec is required"
}Error Response (409) - Singleton guard or queue concurrency limit blocking execution:
{
"code": "max_run_reached",
"message": "DAG ad-hoc-extract is already running, cannot start"
}Example:
curl -X POST "http://localhost:8080/api/v1/dag-runs?remoteNode=local" \
-H "Content-Type: application/json" \
-d '{
"spec": "steps:\n - name: echo\n run: echo \"hello\"",
"name": "adhoc-hello",
"params": "{\"requestedBy\":\"cli\"}"
}'Enqueue DAG Run from Inline Spec
Endpoint: POST /api/v1/dag-runs/enqueue
Queues a DAG-run from an inline YAML spec without persisting a DAG file. The run follows queue semantics (global queue concurrency limits, queue overrides) and is started by the scheduler.
Inline specifications have no source directory, so they cannot supply file dependencies for distributed execution. Save the workflow as a DAG file and enqueue the saved DAG when a worker needs files stored beside it.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Target remote node for queuing | "local" |
Request Body:
{
"spec": "steps:\n - name: sleep\n run: sleep 60",
"name": "queued-sleeper",
"params": "{\"duration\":60}",
"dagRunId": "queue_20241101_010203",
"queue": "low-priority",
"noReuse": false
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| spec | string | DAG specification in YAML format | Yes |
| name | string | Override DAG name if the spec omits one | No |
| params | string | JSON string saved with the queued run | No |
| dagRunId | string | Explicit run ID. When omitted, one is generated | No |
| queue | string | Queue name override for this run only | No |
| noReuse | boolean | If true, recompute eligible build steps when the queued run starts | No |
Response (200):
{
"dagRunId": "queue_20241101_010203"
}Error Response (409) - DAG already queued or queue concurrency limit reached:
{
"code": "max_run_reached",
"message": "DAG queued-sleeper is already in the queue, cannot enqueue"
}Example:
curl -X POST "http://localhost:8080/api/v1/dag-runs/enqueue" \
-H "Content-Type: application/json" \
-d '{
"spec": "steps:\n - name: notify\n run: ./notify.sh",
"name": "queued-notify",
"queue": "alerts"
}'Get DAG Run Details
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}
Fetches detailed status of a specific DAG run. You can use the special value "latest" as the dagRunId to retrieve the most recent DAG run for the specified DAG.
Examples:
GET /api/v1/dag-runs/data-pipeline/20240211_120000- Get a specific runGET /api/v1/dag-runs/data-pipeline/latest- Get the latest run
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| name | string | DAG name |
| dagRunId | string | DAG run ID or "latest" |
When the returned DAG run is still queued, dagRun.conditions can contain queued runtime conditions using the shape described in DAG-run Runtime Conditions. Non-queued DAG runs omit conditions.
Response (200):
{
"dagRun": {
"dagRunId": "20240211_120000",
"name": "data-pipeline",
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-02-11T12:00:00Z",
"finishedAt": "2024-02-11T12:15:00Z",
"params": "{\"date\": \"2024-02-11\", \"env\": \"prod\"}",
"nodes": [
{
"step": {
"name": "extract",
"command": "python extract.py"
},
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-02-11T12:00:00Z",
"finishedAt": "2024-02-11T12:05:00Z",
"retryCount": 0,
"stdout": "/logs/data-pipeline/20240211_120000/extract.out",
"stderr": "/logs/data-pipeline/20240211_120000/extract.err"
},
{
"step": {
"name": "transform",
"command": "python transform.py",
"depends": ["extract"]
},
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-02-11T12:05:00Z",
"finishedAt": "2024-02-11T12:10:00Z",
"retryCount": 0
},
{
"step": {
"name": "load",
"run": "sub-workflow",
"params": "TARGET=warehouse"
},
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-02-11T12:10:00Z",
"finishedAt": "2024-02-11T12:15:00Z",
"subRuns": [
{
"dagRunId": "sub_20240211_121000",
"name": "sub-workflow",
"status": 4,
"statusLabel": "succeeded"
}
]
}
]
}
}startedAt and finishedAt of runs, steps, and sub DAG-runs are RFC 3339 timestamps with millisecond precision, such as 2024-02-11T12:00:00.158Z. A run that an older version stored has second precision.
A node whose step repeats with a repeat mode and no limit carries continuous: true. Its executions are recorded as samples; see Continuous Step Endpoints.
Build Execution Data
For type: build workflows, the DAG-run object includes noReuse, and each node includes a build decision:
{
"dagRun": {
"noReuse": false,
"nodes": [
{
"statusLabel": "succeeded",
"build": {
"decision": "reuse",
"phase": "complete",
"reason": "matched",
"detail": "recipe, inputs, and output match the committed manifest",
"fingerprint": "sha256:...",
"materializationKey": "...",
"producerRun": {
"name": "report-pipeline",
"id": "previous-run-id"
},
"producerAttemptId": "previous-attempt-id"
}
}
]
}
}decision is one of none, always, execute, reuse, or deferred. phase is one of precondition, evaluate, execute, verify, commit, or complete. reason is stable for programmatic handling, while detail is the user-facing explanation. Producer fields are present for reused materializations.
See Build Workflows for the decision rules.
Reused Inputs
A selected-steps run lists where the carried outputs of its skipped steps came from in dagRun.reusedInputs, one entry per skipped step, in DAG step order. The field is absent when the run carried no outputs.
{
"dagRun": {
"reusedInputs": [
{
"step": "test",
"runId": "20241101_080000",
"capturedAt": "2024-11-01T08:00:12.480Z",
"startedAt": "2024-11-01T08:00:10.105Z",
"finishedAt": "2024-11-01T08:00:12.480Z",
"via": "run"
}
]
}
}via is run when the outputs came from outputsFromRunId or outputsFromRuns. startedAt and finishedAt are then the step's times in that run, and capturedAt is its finish time. via is cache when the outputs came from outputs with an outputsSources entry. runId and capturedAt then come from that entry, and the step times are absent. The record stays on the run for all its attempts.
Model Usage
A node whose step called models carries modelUsage.models: one entry per provider and model, over every attempt of the step, including what a coding agent the step ran reported. modelUsage.notReported names a tool whose usage could not be read. dagRun.modelUsage.models totals every step of the run, handlers included.
{
"provider": "openai",
"model": "gpt-5",
"calls": 3,
"inputTokens": 5210,
"cachedInputTokens": 4096,
"cacheWriteTokens": 0,
"outputTokens": 812,
"reasoningTokens": 256,
"cost": 0.0123,
"costReportedCalls": 3
}inputTokens includes cached and cache-write tokens, and outputTokens includes reasoning tokens. cost is in US dollars, as the provider reported it for costReportedCalls of the calls. It covers every call only when costReportedCalls equals calls.
The body steps of a foreach item, as returned by GET /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/foreach/{item}, carry modelUsage and, when they kept one, such as the operations of a browser step, agentSession. Both have the shape that the run's own nodes have, with the run's secrets masked.
Stop or Cancel DAG Run
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/stop
Forcefully stops a running DAG run, or cancels a failed root DAG run that is still pending DAG-level automatic retry.
Response (200): Success
Error Response (404):
{
"code": "not_found",
"message": "DAG run not found"
}Error Response (400):
{
"code": "not_running",
"message": "DAG is not currently running"
}Stop All DAG Runs
Endpoint: POST /api/v1/dags/{fileName}/stop-all
Forcefully stops all currently running instances of a DAG. This is useful when multiple instances of the same DAG are running simultaneously.
Response (200):
{
"errors": []
}Error Response (404):
{
"code": "not_found",
"message": "DAG not found"
}Retry DAG Run
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/retry
Creates a new DAG run based on a previous execution.
Request Body:
{
"dagRunId": "new-run-id"
}Response (200): Success
Error Response (404):
{
"code": "not_found",
"message": "Original DAG run not found"
}Error Response (400):
{
"code": "already_running",
"message": "Another instance of this DAG is already running"
}Error Response (409) - The DAG run is ephemeral.
Reschedule DAG Run
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/reschedule
Launches a fresh DAG run by reusing the captured parameters from a historic execution. Useful for re-running successful or failed runs while keeping the original parameter set.
Request Body:
{
"dagRunId": "optional-new-id",
"dagName": "optional-name-override"
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| dagRunId | string | Custom ID for the new run. If omitted a unique ID is generated. | No |
| dagName | string | Override the DAG name used for the rescheduled run. Must pass DAG name validation. | No |
Rescheduled runs always respect singleton mode. When the target DAG already has active or queued runs, the request is rejected.
Response (200):
{
"dagRunId": "20241101_010203_xyz",
"queued": false
}Error Response (409) - When the DAG already has active or queued runs, or the historic run is ephemeral:
{
"code": "max_run_reached",
"message": "DAG example_dag is already running, cannot start"
}Error Response (404):
{
"code": "not_found",
"message": "DAG run not found"
}Dequeue DAG Run
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/dequeue
Removes a queued DAG run from the queue.
Response (200): Success
Error Response (404):
{
"code": "not_found",
"message": "DAG run not found in queue"
}Log Endpoints
Get DAG Run Log
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/log
Fetches the execution log for a DAG run.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| tail | integer | Lines from end | - |
| head | integer | Lines from start | - |
| offset | integer | Start line (1-based) | - |
| limit | integer | Max lines (max 10000) | - |
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"content": "2024-02-12 14:00:00 INFO DAG data_processing_pipeline started\n2024-02-12 14:00:00 INFO Run ID: 20240212_140000_abc123\n2024-02-12 14:00:00 INFO Parameters: {\"date\": \"2024-02-12\", \"env\": \"production\", \"batch_size\": 5000}\n2024-02-12 14:00:00 INFO Checking preconditions...\n2024-02-12 14:00:01 INFO Precondition passed: Weekday check (current day: 1)\n2024-02-12 14:00:01 INFO Starting step: extract_data\n2024-02-12 14:00:30 INFO [extract_data] Executing: python extract.py --date 2024-02-12\n2024-02-12 14:15:45 INFO [extract_data] Step completed successfully\n2024-02-12 14:15:45 INFO [extract_data] Output saved to variable: EXTRACTED_FILE = /tmp/extracted_20240212.csv\n2024-02-12 14:15:45 INFO Starting step: transform_data\n2024-02-12 14:15:45 INFO [transform_data] Executing: python transform.py --input=/tmp/extracted_20240212.csv\n2024-02-12 14:30:20 INFO [transform_data] Step completed successfully\n2024-02-12 14:30:20 INFO [transform_data] Output saved to variable: TRANSFORMED_FILE = /tmp/transformed_20240212.csv\n2024-02-12 14:30:20 INFO Starting step: load_to_warehouse\n2024-02-12 14:30:20 INFO [load_to_warehouse] Running sub DAG: warehouse-loader\n2024-02-12 14:30:20 INFO [load_to_warehouse] Sub DAG started with ID: sub_20240212_143020_xyz456\n2024-02-12 14:45:30 INFO [load_to_warehouse] Sub DAG completed successfully\n2024-02-12 14:45:30 INFO Executing onSuccess handler: notify_success\n2024-02-12 14:45:32 INFO [notify_success] Handler completed\n2024-02-12 14:45:32 INFO Executing onExit handler: cleanup\n2024-02-12 14:45:35 INFO [cleanup] Handler completed\n2024-02-12 14:45:35 INFO DAG completed successfully\n",
"lineCount": 22,
"totalLines": 156,
"hasMore": true,
"isEstimate": false
}Get Step Log
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/log
Fetches the log for a specific step.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| stream | string | "stdout" or "stderr" | "stdout" |
| tail | integer | Lines from end | - |
| head | integer | Lines from start | - |
| offset | integer | Start line (1-based) | - |
| limit | integer | Max lines (max 10000) | - |
Response (200):
{
"content": "2024-02-11 12:05:00 INFO Starting data transformation...\n2024-02-11 12:05:01 INFO Processing 1000 records\n2024-02-11 12:05:05 INFO Transformation complete\n",
"lineCount": 3,
"totalLines": 3,
"hasMore": false,
"isEstimate": false
}Response with stderr (200):
{
"content": "2024-02-11 12:05:02 WARNING Duplicate key found, skipping record ID: 123\n2024-02-11 12:05:03 WARNING Invalid date format in record ID: 456\n",
"lineCount": 2,
"totalLines": 2,
"hasMore": false,
"isEstimate": false
}DAG Run Outputs Endpoint
Get DAG Run Outputs
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/outputs
Retrieves the collected outputs from a completed DAG run. This endpoint reads the run's outputs.json, which is populated by string-form output: NAME, stdout.outputs, and action: outputs.write.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| name | string | DAG name |
| dagRunId | string | DAG run ID |
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"metadata": {
"dagName": "data-processing-pipeline",
"dagRunId": "20240211_140000_abc123",
"attemptId": "attempt_20240211_140000_001",
"status": "succeeded",
"completedAt": "2024-02-11T14:45:30Z",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\"}"
},
"outputs": {
"extractedFile": "/tmp/extracted_20240211.csv",
"recordCount": "50000",
"transformedFile": "/tmp/transformed_20240211.csv"
}
}Response Fields:
metadata: Execution context for the outputsdagName: Name of the executed DAGdagRunId: Unique identifier for the DAG runattemptId: Identifier for the specific execution attemptstatus: Final status of the DAG run ("succeeded", "partially_succeeded", "failed", "rejected", "aborted")completedAt: RFC3339 timestamp when execution completedparams: JSON-serialized parameters passed to the DAG
outputs: Key-value pairs of collected outputs- String-form
output: NAME,stdout.outputs, andoutputs.writevalues appear here - String-form
output: NAMEkeys are converted fromSCREAMING_SNAKE_CASEtocamelCase;stdout.outputsandoutputs.writekeys are preserved - Object-form
output: {...}values are step-scoped only and are not returned by this endpoint
- String-form
Response when no outputs (200):
{
"metadata": {
"dagName": "",
"dagRunId": "",
"attemptId": "",
"status": "",
"completedAt": "0001-01-01T00:00:00Z"
},
"outputs": {}
}Error Response (404):
{
"code": "not_found",
"message": "dag-run ID 20240211_140000_abc123 not found for DAG data-processing-pipeline"
}Notes:
- Outputs are only available for completed DAG runs (succeeded, partially_succeeded, failed, rejected, or aborted)
- Secret values in outputs are automatically masked with
******* - Object-form
output: {...}does not participate in run-level outputs unless values are republished throughstdout.outputsoroutputs.write
Example:
# Get outputs from a specific DAG run
curl "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/20240211_140000_abc123/outputs" \
-H "Authorization: Bearer your-token"
# Get outputs from the latest DAG run
curl "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/latest/outputs" \
-H "Authorization: Bearer your-token"Get Step Outputs
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/outputs
Returns the persisted outputs of one step. stepName is a step name or ID, a handler step's name or ID, or a handler key such as handler_on.exit.
Response (200):
{
"stepName": "fetch_data",
"stepId": "fetch",
"status": 4,
"statusLabel": "succeeded",
"outputs": [
{"name": "body", "value": "{\"count\":42}", "truncated": false, "size": 12}
],
"outputVariable": {"name": "RESULT", "value": "ok", "truncated": false, "size": 2}
}outputsholds the step's declared outputs, sorted by name, as${steps.<id>.outputs.<name>}resolves them. For a step that declares none, it holds the published values.outputVariableis the value of the step's string-formoutput: NAME, when it has one.- Each value is cut at 65536 bytes.
truncatedistruewhen it was cut, andsizeis its length in bytes before the cut. - Secret values are masked before they are persisted.
- Returns
404when the DAG run or the step does not exist.
Continuous Step Endpoints
A continuous step is a step whose repeat_policy has a repeat mode and no limit. Its node in the run details carries continuous: true. Instead of keeping every execution in the run status, Dagu records one sample per execution with its start time, duration, and exit code. A sample with a non-zero exit code is a failed sample. These endpoints read the samples of one step. They accept the optional remoteNode query parameter.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/samples | Samples summarized in time buckets |
GET | /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/events | Starts, stops, failures, and recoveries, newest first |
GET | /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/availability | Share of samples that succeeded over the last 24 hours, 7 days, 30 days, and 365 days |
A request for a run or step that does not exist, or for a step that is not continuous, returns 404. A query parameter out of its range, from after to, or a before cursor that the server did not give returns 400.
Get Step Samples
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| from | string | Start of the window (RFC 3339) | 24 hours before to |
| to | string | End of the window (RFC 3339) | now |
| points | integer | Most buckets to return (10-2000) | 600 |
The server keeps samples in four tiers: 1-second buckets for 24 hours, 1-minute buckets for 7 days, 1-hour buckets for 30 days, and 24-hour buckets with no limit. It answers from the finest tier whose retention covers from and whose bucket count in the window is at most points.
Response (200):
{
"resolutionSeconds": 60,
"from": "2026-10-31T00:00:00Z",
"to": "2026-11-01T00:00:00Z",
"buckets": [
{"time": "2026-10-31T00:00:00Z", "count": 60, "failures": 1, "minMs": 12, "maxMs": 380, "p50Ms": 18, "p95Ms": 44}
]
}resolutionSeconds is 1, 60, 3600, or 86400. Buckets are in time order and start in UTC. A bucket with no sample is not listed.
Get Step Events
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| from | string | Start of the window (RFC 3339), inclusive | no bound |
| to | string | End of the window (RFC 3339), exclusive | no bound |
| limit | integer | Most events to return (1-500) | 100 |
| before | string | nextCursor from a previous response | - |
Response (200):
{
"events": [
{"time": "2026-11-01T09:12:05.120Z", "kind": "recovered", "exitCode": 0, "failedCount": 42, "outageSeconds": 42},
{"time": "2026-11-01T09:11:23.004Z", "kind": "failed", "exitCode": 7, "message": "connection refused"}
],
"nextCursor": "..."
}kind is started, stopped, failed, or recovered. message is the last line of a failed execution's standard error, 256 bytes at most. failedCount counts the failed samples of the outage, and outageSeconds is its length. A failed event is also recorded after each hour of an outage. nextCursor is absent when there are no more events.
Get Step Availability
Response (200):
{
"windows": [
{"window": "24h", "count": 86400, "failures": 42, "availability": 0.99951},
{"window": "7d", "count": 604800, "failures": 42, "availability": 0.99993},
{"window": "30d", "count": 2592000, "failures": 310, "availability": 0.99988},
{"window": "365d", "count": 2592000, "failures": 310, "availability": 0.99988}
],
"streak": 3175,
"lastFailureAt": "2026-11-01T09:12:04.950Z",
"totalCount": 2592000
}availability is the share of samples that succeeded, from 0 to 1, and is absent when count is 0. streak counts the samples since the last failed sample, and lastFailureAt is that sample's time; it is absent when no sample failed. Once the 1-second tier no longer holds that sample, lastFailureAt is the start of the bucket that holds it, and streak is a lower bound. totalCount counts every execution.
See Repeat Without a Limit for continuous steps.
Step Management Endpoints
Update Step Status
Endpoint: PATCH /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/status
Manually updates a step's execution status.
Request Body:
{
"status": 4
}Status Values:
- 0: Not started
- 1: Running
- 2: Failed
- 3: Aborted
- 4: Success
- 5: Skipped
- 6: Partial Success
- 7: Waiting
- 8: Rejected
- 9: Retrying
Response (200): Success
Error Response (404):
{
"code": "not_found",
"message": "Step not found in DAG run"
}Error Response (400):
{
"code": "bad_request",
"message": "Invalid status value"
}Human Task Endpoints
These endpoints complete or push back standalone action: human.task steps. They operate on root DAG runs regardless of whether the run executes locally or on a distributed worker. The caller needs permission to execute DAGs in the run's workspace.
After completion, DAG-run node responses expose the actor as humanTaskCompletedBy and humanTaskCompletedById. These fields may be absent on older runs or when authentication is disabled.
Complete Human Task
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/human-tasks/{stepId}/complete
Validates typed input against the form stored with the run, then durably completes the human-task step. The request body is the form input object itself, not an inputs wrapper.
Request Body:
{
"environment": "production",
"replicas": 3,
"notify": true
}Send {} for an acknowledgement-only task. The request body is limited to 16 MiB.
Response (200):
{
"dagName": "release-workflow",
"dagRunId": "20260720_120000",
"stepId": "review",
"alreadyCompleted": false,
"queued": true,
"remainingWaitingSteps": 0,
"resumeRequested": true
}| Field | Type | Description |
|---|---|---|
dagName | string | Name of the root DAG. |
dagRunId | string | Concrete DAG-run ID. |
stepId | string | Explicit ID of the completed human-task step. |
alreadyCompleted | boolean | Whether identical input had already completed this task. |
queued | boolean | Whether this request durably added the DAG-run retry to the queue. |
remainingWaitingSteps | integer | Number of manual steps that still need input. |
resumeRequested | boolean | Whether this request found the DAG run waiting and ready to resume, whether it queued the resume or a concurrent request queued it first. false when the run keeps waiting or has already left Waiting. |
Dagu enqueues a retry of the same DAG run when the completion unblocks a step or no manual steps remain waiting; completion never starts the DAG immediately. A step counts as unblocked when every dependency lets it run, it declares no build inputs, and no step in the run has failed or been aborted. Otherwise Dagu stores the completion and returns queued: false and resumeRequested: false. While a resumed attempt is queued or running, completing another open task returns 409. A repeated request with the same canonical input is idempotent. Different input for an already completed task returns a conflict.
Error Responses:
400: The body is not a JSON object or does not satisfy the stored form.404: The DAG run or human-task step does not exist or is not visible to the caller.409: The task is not actionable, the run changed concurrently, or prior input conflicts.413: The request body exceeds 16 MiB.503: The completion was stored, but the DAG-run retry could not be queued.
A 503 with error code human_task_resume_failed includes completionStored: true and resumePending: true in details. Do not ask the operator to re-enter the form. Retry only the queue operation with the endpoint below.
Retry Human Task Resume Queue
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/human-tasks/resume
Queues a recoverable human-task checkpoint after a completion or push-back was stored but the original enqueue attempt failed. This endpoint has no request body and never needs the previously submitted form values or feedback.
Response (200):
{
"dagName": "release-workflow",
"dagRunId": "20260720_120000",
"queued": true
}The operation is safe to retry. queued is false when the retry is already queued or the run has already resumed.
Error Responses:
404: The DAG run does not exist or is not visible to the caller.409: Manual steps are still waiting and no step is ready to run, or the run has no completed human-task checkpoint or stored push-back to resume.503: The queue attempt failed again and remains retryable.
Push Back Human Task
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/human-tasks/{stepId}/push-back
Sends a waiting human task that declares with.push_back back to its rewind target. Dagu validates the feedback against the stored push-back form, then atomically resets the rewind target and every step that depends on it, including the task, with the feedback as push-back context. The request body is the feedback object itself, not an inputs wrapper.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
expectedIteration | integer | Optional. Push-back iteration of the task the caller reviewed, 0 before the first push-back. A different current iteration returns 409 without changes. |
Request Body:
{
"feedback": "Add coverage for the empty input case"
}Send {} when the task declares no push-back form. The request body is limited to 16 MiB.
Response (200):
{
"dagName": "review-loop",
"dagRunId": "20260720_120000",
"stepId": "review",
"rewindTo": "implement",
"iteration": 1,
"alreadyPushedBack": false,
"queued": true,
"resumeRequested": true
}| Field | Type | Description |
|---|---|---|
dagName | string | Name of the root DAG. |
dagRunId | string | Concrete DAG-run ID. |
stepId | string | Explicit ID of the pushed-back human-task step. |
rewindTo | string | Name of the step that runs again first. |
iteration | integer | Push-back iteration the push-back recorded. |
alreadyPushedBack | boolean | Whether identical feedback had already pushed the task back and the task has not opened again. |
queued | boolean | Whether this request durably added the DAG-run retry to the queue. |
resumeRequested | boolean | Whether the run was ready to resume, whether this request or a concurrent one queued it. false when the run keeps waiting for another step or has already left Waiting. |
The push-back is stored before the run is queued. Until the task opens again, an identical repeated request only retries the queue and returns alreadyPushedBack: true; different feedback returns 409. After the task reopens, a new request starts a new iteration, so send expectedIteration to make a stale request fail instead of pushing back a task that already reopened. Feedback is limited to 16 KiB as JSON.
Error Responses:
400: The body is not a JSON object, does not satisfy the stored push-back form, exceeds 16 KiB as recorded feedback, or the task declares nowith.push_back.404: The DAG run or human-task step does not exist or is not visible to the caller.409: The task is not open, is at a different push-back iteration, was already pushed back with different feedback, or the run changed concurrently.413: The request body exceeds 16 MiB.503: The push-back was stored, but the DAG-run retry could not be queued.
A 503 with error code human_task_resume_failed includes pushBackStored: true and resumePending: true in details. Retry the same request or the resume endpoint; neither pushes the task back again.
Approval Endpoints
Separate manual-step APIs
The approve, reject, and push-back endpoints below resolve executable steps configured with approval. They do not operate on action: human.task; use Push Back Human Task instead.
DAG-run node responses expose actor names and IDs in approvedBy/approvedById, rejectedBy/rejectedById, and each pushBackHistory entry's by/byId. These fields may be absent on older runs or when authentication is disabled.
Approve Step
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/approve
Approves a step that is in Waiting status and resumes the run when a step can run. In a root run, the resume happens even while other manual steps keep waiting; see Parallel Approvals. If the step's approval.input defines input fields, the provided inputs are available as environment variables in subsequent steps.
Request Body (optional):
{
"inputs": {
"FEEDBACK": "Looks good, proceed.",
"PRIORITY": "high"
}
}| Field | Type | Description |
|---|---|---|
inputs | object | Key-value pairs injected as environment variables in subsequent steps. Keys must match approval.input if defined. |
Response (200):
{
"dagRunId": "20240211_140000_abc123",
"stepName": "deploy-staging",
"resumed": true
}| Field | Type | Description |
|---|---|---|
dagRunId | string | The DAG run ID |
stepName | string | The approved step name |
resumed | boolean | Whether resume execution was accepted, directly or through a queue. true does not mean execution has started. |
Error Responses:
400: Step is not in Waiting status, or required inputs missing404: DAG-run or step not found409: The approval was saved, but the run changed before its resume could be accepted503: The approval was saved, but the resume could not be accepted
A 503 with error code internal_error includes approvalStored: true and resumePending: true in details. Do not approve again. Retry only the resume with Resume After Approval.
Sub DAG variant: POST /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/approve
The sub-DAG variant resumes the child only after every waiting step inside it is resolved. It returns resumed: false instead of 503 when that resume fails.
Resume After Approval
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/resume
Resumes a root run whose approval is saved but whose resume was not accepted. This endpoint has no request body; it never changes approvals or their inputs. It requires permission to run DAGs.
Response (200):
{
"dagRunId": "20240211_140000_abc123",
"resumed": true
}The operation is safe to retry. A run that is already queued or running returns resumed: true without starting another execution.
Error Responses:
404: The DAG run does not exist.409: The run has no saved root approval, no approved work is ready to resume, or the run changed before the resume could be accepted.503: The resume could not be accepted again and remains retryable.
Reject Step
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/reject
Rejects a step that is in Waiting status. The step transitions to Rejected, the DAG status becomes rejected, dependent steps are aborted, and the failure handler runs.
Request Body (optional):
{
"reason": "Output quality insufficient"
}| Field | Type | Description |
|---|---|---|
reason | string | Optional reason for rejection |
Response (200):
{
"dagRunId": "20240211_140000_abc123",
"stepName": "deploy-staging"
}Error Responses:
400: Step is not in Waiting status404: DAG-run or step not found
Sub DAG variant: POST /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/reject
Push Back Step
Endpoint: POST /api/v1/dag-runs/{name}/{dagRunId}/steps/{stepName}/push-back
Pushes back a Waiting step for re-execution with feedback. By default, the waiting approval step itself is the restart point. If approval.rewind_to is configured, Dagu resets that earlier step and all of its transitive dependents to NotStarted. The approvalIteration counter increments, and every step that later re-executes within the rewound scope receives the provided inputs as environment variables plus the DAG_PUSHBACK JSON payload.
Request Body (optional):
{
"inputs": {
"FEEDBACK": "Include error counts in the summary",
"FORMAT": "markdown"
}
}| Field | Type | Description |
|---|---|---|
inputs | object | Key-value pairs injected as environment variables when rewound steps re-execute. If a step declares approval.input, only those declared keys are exposed on that step. |
Response (200):
{
"dagRunId": "20240211_140000_abc123",
"stepName": "generate-report",
"approvalIteration": 2,
"resumed": true
}| Field | Type | Description |
|---|---|---|
dagRunId | string | The DAG run ID |
stepName | string | The pushed-back step name |
approvalIteration | integer | The approval iteration count after push-back |
resumed | boolean | Whether the DAG run was re-enqueued for execution |
Error Responses:
400: Step is not in Waiting status, step has no approval config, or required inputs missing404: DAG-run or step not found
Sub DAG variant: POST /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/push-back
Search Endpoints
Search DAGs
Endpoint: GET /api/v1/dags/search
Performs full-text search across DAG definitions. This endpoint remains available for direct DAG search requests.
Query Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
| q | string | Search query | Yes |
| remoteNode | string | Remote node name | No |
Response (200):
{
"results": [
{
"name": "database_backup",
"dag": {
"name": "database_backup",
"group": "Operations",
"schedule": [{"expression": "0 0 * * 0"}],
"description": "Weekly database backup job",
"params": ["target_db", "retention_days"],
"defaultParams": "{\"retention_days\": 30}",
"labels": ["backup", "weekly", "critical"]
},
"matches": [
{
"line": " command: pg_dump ${params.target_db} | gzip > backup_$(date +%Y%m%d).sql.gz",
"lineNumber": 25,
"startLine": 20
},
{
"line": "description: Weekly database backup job",
"lineNumber": 3,
"startLine": 1
}
]
},
{
"name": "data_processing_pipeline",
"dag": {
"name": "data_processing_pipeline",
"group": "ETL",
"schedule": [
{"expression": "0 2 * * *"},
{"expression": "0 14 * * *"}
],
"description": "Daily data processing pipeline for warehouse ETL",
"params": ["date", "env", "batch_size"],
"defaultParams": "{\"batch_size\": 1000, \"env\": \"dev\"}",
"labels": ["production", "etl", "daily"]
},
"matches": [
{
"line": " command: psql -h ${env.DB_HOST} -d analytics -c \"COPY data TO STDOUT\"",
"lineNumber": 45,
"startLine": 42
}
]
}
],
"errors": []
}Search DAG Feed
Endpoint: GET /api/v1/search/dags
Returns lightweight cursor-based DAG search results for the global search page.
Query Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
| q | string | Search query | Yes |
| limit | integer | Number of results to return (max 50) | No |
| cursor | string | Opaque cursor returned by a previous response | No |
| remoteNode | string | Remote node name | No |
| workspace | all, default, or workspace name | Workspace to search. Omitted defaults to all. | No |
Each result includes preview snippets plus hasMoreMatches and nextMatchesCursor for loading more snippets from that result.
Response (200):
{
"results": [
{
"fileName": "database_backup",
"name": "database_backup",
"hasMoreMatches": true,
"nextMatchesCursor": "eyJmaWxlTmFtZSI6ImRhdGFiYXNlX2JhY2t1cCJ9",
"matches": [
{
"line": " command: pg_dump ${params.target_db} | gzip > backup_$(date +%Y%m%d).sql.gz",
"lineNumber": 25,
"startLine": 20
}
]
}
],
"hasMore": true,
"nextCursor": "eyJmaWxlTmFtZSI6ImRhdGFiYXNlX2JhY2t1cCJ9"
}Search DAG Match Snippets
Endpoint: GET /api/v1/search/dags/{fileName}/matches
Loads additional cursor-based snippets for one DAG search result.
Query Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
| q | string | Search query | Yes |
| limit | integer | Number of snippets to return (max 50) | No |
| cursor | string | Opaque cursor returned by a previous snippet response | No |
| remoteNode | string | Remote node name | No |
| workspace | default or workspace name | Workspace for the selected DAG result. Omitted defaults to default. | No |
Response (200):
{
"matches": [
{
"line": "description: Weekly database backup job",
"lineNumber": 3,
"startLine": 1
}
],
"hasMore": false
}Event Logs
Endpoint: GET /api/v1/event-logs
Returns centralized operational event log entries. When builtin authentication is enabled, this endpoint requires a manager or admin role.
Query Parameters:
| Parameter | Type | Description | Required |
|---|---|---|---|
| kind | string | Event kind such as dag_run or llm_usage | No |
| type | string | Event type such as dag.run.failed or llm.usage.recorded | No |
| dagName | string | Filter by DAG name | No |
| dagRunId | string | Filter by DAG run ID | No |
| attemptId | string | Filter by attempt ID | No |
| sessionId | string | Filter by session ID | No |
| userId | string | Filter by user ID | No |
| model | string | Filter by model name | No |
| startTime | string | ISO 8601 start time filter | No |
| endTime | string | ISO 8601 end time filter | No |
| limit | integer | Number of entries to return (max 500) | No |
| offset | integer | Offset for compatibility pagination | No |
| paginationMode | string | offset or cursor | No |
| cursor | string | Opaque cursor for older entries | No |
| remoteNode | string | Remote node name | No |
If cursor is provided, cursor pagination is used automatically and offset must be omitted.
Response (200):
{
"entries": [
{
"id": "dag_4ac3b3b9b16bdb10",
"schemaVersion": 1,
"occurredAt": "2026-04-05T08:05:12Z",
"recordedAt": "2026-04-05T08:05:12Z",
"kind": "dag_run",
"type": "dag.run.failed",
"sourceService": "scheduler",
"sourceInstance": "worker-1:12345",
"dagName": "nightly-report",
"dagRunId": "20260405_080000_abc123",
"attemptId": "attempt-1",
"status": "failed",
"data": {
"trigger": "scheduler"
}
}
],
"nextCursor": "eyJvY2N1cnJlZEF0IjoiMjAyNi0wNC0wNVQwODowNToxMloiLCJpZCI6ImRhZ180YWMzYjNiOWIxNmJkYjEwIn0"
}Get All Labels
Endpoint: GET /api/v1/dags/labels
Retrieves all unique labels used across DAGs.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"labels": [
"backup",
"critical",
"daily",
"data-quality",
"etl",
"experimental",
"hourly",
"maintenance",
"ml",
"monitoring",
"production",
"reporting",
"staging",
"testing",
"weekly"
],
"errors": []
}Response with Errors (200):
{
"labels": [
"backup",
"critical",
"daily",
"etl",
"production"
],
"errors": [
"Error reading DAG file: malformed-etl.yaml - yaml: line 15: found unexpected end of stream",
"Error reading DAG file: invalid-syntax.yaml - yaml: unmarshal errors:\n line 8: field invalidField not found in type digraph.DAG"
]
}Monitoring Endpoints
Prometheus Metrics
Endpoint: GET /api/v1/metrics
Returns Prometheus-compatible metrics.
Response (200) (text/plain):
# HELP dagu_info Dagu build information
# TYPE dagu_info gauge
dagu_info{version="1.14.0",build_date="2024-01-01T12:00:00Z",go_version="1.21"} 1
# HELP dagu_uptime_seconds Time since server start
# TYPE dagu_uptime_seconds gauge
dagu_uptime_seconds 3600
# HELP dagu_dag_runs_currently_running Number of currently running DAG runs
# TYPE dagu_dag_runs_currently_running gauge
dagu_dag_runs_currently_running 5
# HELP dagu_dag_runs_queued_total Total number of DAG runs in queue
# TYPE dagu_dag_runs_queued_total gauge
dagu_dag_runs_queued_total 8
# HELP dagu_dag_runs_total Total number of DAG runs by status
# TYPE dagu_dag_runs_total counter
dagu_dag_runs_total{status="succeeded"} 2493
dagu_dag_runs_total{status="failed"} 15
dagu_dag_runs_total{status="aborted"} 7
# HELP dagu_dags_total Total number of DAGs
# TYPE dagu_dags_total gauge
dagu_dags_total 45
# HELP dagu_scheduler_running Whether the scheduler is running
# TYPE dagu_scheduler_running gauge
dagu_scheduler_running 1Error Handling
All endpoints return structured error responses:
{
"code": "error_code",
"message": "Human readable error message",
"details": {
"additional": "error details"
}
}Error Codes:
| Code | Description |
|---|---|
| forbidden | Insufficient permissions |
| bad_request | Invalid request parameters |
| not_found | Resource doesn't exist |
| internal_error | Server-side error |
| unauthorized | Authentication failed |
| bad_gateway | Upstream service error |
| remote_node_error | Remote node connection failed |
| already_running | DAG is already running |
| not_running | DAG is not running |
| already_exists | Resource already exists |
Sub DAG Run Endpoints
Get Sub DAG Runs with Timing Information
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs
Retrieves timing and status information for all sub DAG runs within a specific DAG run. This is useful for tracking repeated executions of sub DAG steps and understanding the timeline of sub workflows.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| name | string | DAG name |
| dagRunId | string | DAG run ID |
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"subRuns": [
{
"dagRunId": "sub_20240211_143020_001",
"params": "{\"file\": \"/tmp/data_batch_1.csv\", \"table\": \"fact_sales\"}",
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-02-11T14:30:20Z",
"finishedAt": "2024-02-11T14:35:45Z"
},
{
"dagRunId": "sub_20240211_143600_002",
"params": "{\"file\": \"/tmp/data_batch_2.csv\", \"table\": \"fact_sales\"}",
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-02-11T14:36:00Z",
"finishedAt": "2024-02-11T14:41:30Z"
},
{
"dagRunId": "sub_20240211_144200_003",
"params": "{\"file\": \"/tmp/data_batch_3.csv\", \"table\": \"fact_sales\"}",
"status": 1,
"statusLabel": "running",
"startedAt": "2024-02-11T14:42:00Z",
"finishedAt": null
}
]
}Response Fields:
subRuns: Array of sub DAG run details with timing informationdagRunId: Unique identifier for the sub DAG runparams: JSON string of parameters passed to the sub DAGstatus: Execution status (0-8, see status values below)statusLabel: Human-readable status labelstartedAt: ISO 8601 timestamp when the sub DAG run startedfinishedAt: ISO 8601 timestamp when the sub DAG run finished (null if still running)
Status Values:
- 0: Not started
- 1: Running
- 2: Failed
- 3: Aborted
- 4: Success
- 5: Queued
- 6: Partial Success
- 7: Waiting
- 8: Rejected
Error Response (404):
{
"code": "not_found",
"message": "DAG run not found"
}Use Case: This endpoint is particularly useful for nodes with repeat_policy that execute sub DAGs multiple times. It allows the UI to display a timeline of all executions with their respective start times, end times, and statuses, making it easier to track and debug repeated sub workflow executions.
Get Sub DAG Run Details
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}
Fetches detailed status of a sub DAG run.
Response (200):
{
"dagRunDetails": {
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_140000_abc123",
"parentDAGRunName": "data_processing_pipeline",
"parentDAGRunId": "20240211_140000_abc123",
"dagRunId": "sub_20240211_143020_xyz456",
"name": "warehouse_loader_subdag",
"status": 4,
"statusLabel": "succeeded",
"queuedAt": "",
"startedAt": "2024-02-11T14:30:20Z",
"finishedAt": "2024-02-11T14:45:30Z",
"log": "/logs/warehouse_loader_subdag/sub_20240211_143020_xyz456.log",
"params": "{\"file\": \"/tmp/transformed_20240211.csv\", \"table\": \"fact_sales\"}",
"nodes": [
{
"step": {
"name": "validate_schema",
"command": "validate_schema.py",
"args": [],
"depends": []
},
"stdout": "/logs/warehouse_loader_subdag/sub_20240211_143020_xyz456/validate_schema.stdout",
"stderr": "/logs/warehouse_loader_subdag/sub_20240211_143020_xyz456/validate_schema.stderr",
"startedAt": "2024-02-11T14:30:20Z",
"finishedAt": "2024-02-11T14:30:35Z",
"status": 4,
"statusLabel": "succeeded",
"retryCount": 0,
"doneCount": 1,
"subRuns": [],
"subRunsRepeated": [],
"error": ""
},
{
"step": {
"name": "load_data",
"command": "load_to_warehouse.py",
"depends": ["validate_schema"]
},
"stdout": "/logs/warehouse_loader_subdag/sub_20240211_143020_xyz456/load_data.stdout",
"stderr": "/logs/warehouse_loader_subdag/sub_20240211_143020_xyz456/load_data.stderr",
"startedAt": "2024-02-11T14:30:35Z",
"finishedAt": "2024-02-11T14:45:30Z",
"status": 4,
"statusLabel": "succeeded",
"retryCount": 0,
"doneCount": 1,
"subRuns": [],
"subRunsRepeated": [],
"error": ""
}
],
"onExit": null,
"onSuccess": null,
"onFailure": null,
"onAbort": null,
"preconditions": []
}
}Get Sub DAG Run Log
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/log
Fetches the log for a sub DAG run.
Response (200):
{
"content": "2024-02-11 14:30:20 INFO Starting sub DAG: warehouse_loader_subdag\n2024-02-11 14:30:20 INFO Parameters: {\"file\": \"/tmp/transformed_20240211.csv\", \"table\": \"fact_sales\"}\n2024-02-11 14:30:20 INFO Parent DAG: data_processing_pipeline (20240211_140000_abc123)\n2024-02-11 14:30:20 INFO Step 'validate_schema' started\n2024-02-11 14:30:22 INFO Schema validation: Checking table structure for 'fact_sales'\n2024-02-11 14:30:35 INFO Step 'validate_schema' completed successfully\n2024-02-11 14:30:35 INFO Step 'load_data' started\n2024-02-11 14:30:36 INFO Opening file: /tmp/transformed_20240211.csv\n2024-02-11 14:30:37 INFO File contains 50000 records\n2024-02-11 14:30:38 INFO Beginning bulk insert to warehouse.fact_sales\n2024-02-11 14:35:00 INFO Progress: 25000/50000 records loaded (50%)\n2024-02-11 14:40:00 INFO Progress: 45000/50000 records loaded (90%)\n2024-02-11 14:45:28 INFO All 50000 records loaded successfully\n2024-02-11 14:45:29 INFO Committing transaction\n2024-02-11 14:45:30 INFO Step 'load_data' completed successfully\n2024-02-11 14:45:30 INFO Sub DAG completed successfully\n",
"lineCount": 7,
"totalLines": 7,
"hasMore": false,
"isEstimate": false
}Get Child Step Log
Endpoint: GET /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/log
Fetches the log for a step in a sub DAG run.
Response (200):
{
"content": "2024-02-11 14:30:35 INFO Step 'load_data' started\n2024-02-11 14:30:36 INFO Opening file: /tmp/transformed_20240211.csv\n2024-02-11 14:30:37 INFO File contains 50000 records\n2024-02-11 14:30:38 INFO Beginning bulk insert to warehouse.fact_sales\n2024-02-11 14:30:39 INFO Using batch size: 5000\n2024-02-11 14:30:40 INFO Processing batch 1 of 10\n2024-02-11 14:32:00 INFO Batch 1 complete (5000 records)\n2024-02-11 14:33:20 INFO Processing batch 2 of 10\n2024-02-11 14:34:40 INFO Batch 2 complete (10000 records)\n2024-02-11 14:36:00 INFO Processing batch 3 of 10\n2024-02-11 14:37:20 INFO Batch 3 complete (15000 records)\n2024-02-11 14:38:40 INFO Processing batch 4 of 10\n2024-02-11 14:40:00 INFO Batch 4 complete (20000 records)\n2024-02-11 14:41:20 INFO Processing batch 5 of 10\n2024-02-11 14:42:40 INFO Batch 5 complete (25000 records)\n[... truncated for brevity ...]\n2024-02-11 14:45:28 INFO All 50000 records loaded successfully\n2024-02-11 14:45:29 INFO Committing transaction\n2024-02-11 14:45:30 INFO Step 'load_data' completed successfully\n",
"lineCount": 50,
"totalLines": 156,
"hasMore": true,
"isEstimate": false
}Update Child Step Status
Endpoint: PATCH /api/v1/dag-runs/{name}/{dagRunId}/sub-dag-runs/{subDAGRunId}/steps/{stepName}/status
Updates the status of a step in a sub DAG run.
Request Body:
{
"status": 4
}Response (200): Success
Error Response (404):
{
"code": "not_found",
"message": "Sub DAG run or step not found"
}Queue Management Endpoints
List All Queues
Endpoint: GET /api/v1/queues
Retrieves queue summaries. Use GET /api/v1/queues/{name}/items to fetch paginated running or queued items for a specific queue.
Response (200):
{
"queues": [
{
"name": "critical",
"type": "global",
"maxConcurrency": 2,
"runningCount": 1,
"queuedCount": 2,
"running": [
{
"dagRunId": "20240211_140000_abc123",
"name": "data_processing_pipeline",
"status": 1,
"statusLabel": "running",
"startedAt": "2024-02-11T14:00:00Z",
"finishedAt": "",
"log": "/logs/data_processing_pipeline/20240211_140000_abc123.log"
}
]
},
{
"name": "backup_job",
"type": "dag-based",
"runningCount": 1,
"queuedCount": 0,
"running": [
{
"dagRunId": "20240211_150000_backup",
"name": "backup_job",
"status": 1,
"statusLabel": "running",
"startedAt": "2024-02-11T15:00:00Z",
"finishedAt": "",
"log": "/logs/backup_job/20240211_150000_backup.log"
}
],
"maxConcurrency": 1
}
],
"summary": {
"totalQueues": 2,
"totalRunning": 2,
"totalQueued": 2,
"totalCapacity": 3,
"utilizationPercentage": 66.666664
}
}Response Fields:
queues: Queue summary objectstype:globalfor configured global queues,dag-basedwhen the DAG name itself acts as the queuerunning: Only currently running DAG-runs are embedded heresummary: Capacity and utilization totals across all queues
List Queue Items
Endpoint: GET /api/v1/queues/{name}/items
Returns paginated items for one queue.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| type | string | queued or running | queued |
| page | integer | Page number | 1 |
| perPage | integer | Items per page | 50 |
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"items": [
{
"dagRunId": "20240211_143000_def456",
"name": "ml_training_pipeline",
"status": 5,
"statusLabel": "queued",
"queuedAt": "2024-02-11T14:30:00Z",
"startedAt": "",
"finishedAt": "",
"log": "/logs/ml_training_pipeline/20240211_143000_def456.log",
"conditions": [
{
"type": "Queued",
"status": "True",
"reason": "QueueConcurrencyLimitReached",
"message": "DAG-run is waiting because the queue's active-run concurrency limit has been reached.",
"checkedAt": "2024-02-11T14:31:00Z"
}
]
}
],
"pagination": {
"totalRecords": 2,
"currentPage": 1,
"totalPages": 2,
"nextPage": 2,
"prevPage": null
}
}Error Response (500):
{
"code": "internal_error",
"message": "Failed to retrieve queue information"
}Queued items use the same conditions field as other DAG-run summaries. Running queue items do not include queued runtime conditions.
Additional Endpoints
List DAG Runs by Name
Endpoint: GET /api/v1/dag-runs/{name}
Lists DAG runs for a specific DAG name using forward-only cursor pagination.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| name | string | DAG name |
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| status | integer | Filter by status (0-8) | - |
| fromDate | integer | Unix timestamp start | - |
| toDate | integer | Unix timestamp end | - |
| dagRunId | string | Filter by run ID | - |
| limit | integer | Page size (max 500) | 100 |
| cursor | string | Opaque cursor returned by the previous page | - |
| remoteNode | string | Remote node name | "local" |
Use nextCursor from the previous response to continue loading older runs.
Response (200):
{
"dagRuns": [
{
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_140000_abc123",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_140000_abc123",
"name": "data_processing_pipeline",
"status": 4,
"statusLabel": "succeeded",
"queuedAt": "",
"startedAt": "2024-02-11T14:00:00Z",
"finishedAt": "2024-02-11T14:45:30Z",
"log": "/logs/data_processing_pipeline/20240211_140000_abc123.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}"
},
{
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_020000_def456",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_020000_def456",
"name": "data_processing_pipeline",
"status": 2,
"statusLabel": "failed",
"queuedAt": "",
"startedAt": "2024-02-11T02:00:00Z",
"finishedAt": "2024-02-11T02:15:45Z",
"log": "/logs/data_processing_pipeline/20240211_020000_def456.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}"
}
],
"nextCursor": "eyJvY2N1cnJlZEF0IjoiMjAyNC0wMi0xMVQxNDowMDowMFoiLCJkYWdSdW5JZCI6IjIwMjQwMjExXzE0MzAwMF9tbCJ9"
}Example Usage
Start a DAG with Parameters
curl -X POST "http://localhost:8080/api/v1/dags/data-processing-pipeline/start" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}",
"dagRunId": "manual_20240211_160000"
}'Response:
{
"dagRunId": "manual_20240211_160000"
}Start a DAG with Singleton Mode
curl -X POST "http://localhost:8080/api/v1/dags/critical-job/start" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{
"singleton": true,
"params": "{\"priority\": \"high\"}"
}'Response if DAG is not running (200):
{
"dagRunId": "20240211_161500_xyz789"
}Response if DAG is already running (409):
{
"code": "already_running",
"message": "DAG critical-job is already running, cannot start in singleton mode"
}Check DAG Run Status
curl "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/latest" \
-H "Authorization: Bearer your-token"Response:
{
"dagRunDetails": {
"rootDAGRunName": "data_processing_pipeline",
"rootDAGRunId": "20240211_160000_current",
"parentDAGRunName": "",
"parentDAGRunId": "",
"dagRunId": "20240211_160000_current",
"name": "data_processing_pipeline",
"status": 1,
"statusLabel": "running",
"queuedAt": "",
"startedAt": "2024-02-11T16:00:00Z",
"finishedAt": "",
"log": "/logs/data_processing_pipeline/20240211_160000_current.log",
"params": "{\"date\": \"2024-02-11\", \"env\": \"production\", \"batch_size\": 5000}",
"nodes": [
{
"step": {
"name": "extract_data",
"id": "extract"
},
"status": 4,
"statusLabel": "succeeded",
"startedAt": "2024-02-11T16:00:30Z",
"finishedAt": "2024-02-11T16:15:45Z",
"retryCount": 0,
"doneCount": 1
},
{
"step": {
"name": "transform_data",
"id": "transform"
},
"status": 1,
"statusLabel": "running",
"startedAt": "2024-02-11T16:15:45Z",
"finishedAt": "",
"retryCount": 0,
"doneCount": 0
},
{
"step": {
"name": "load_to_warehouse",
"id": "load"
},
"status": 0,
"statusLabel": "not_started",
"startedAt": "",
"finishedAt": "",
"retryCount": 0,
"doneCount": 0
}
]
}
}Search for DAGs
curl "http://localhost:8080/api/v1/dags/search?q=database" \
-H "Authorization: Bearer your-token"Response:
{
"results": [
{
"name": "database_backup",
"dag": {
"name": "database_backup",
"group": "Operations",
"schedule": [{"expression": "0 0 * * 0"}],
"description": "Weekly database backup job",
"params": ["target_db", "retention_days"],
"defaultParams": "{\"retention_days\": 30}",
"labels": ["backup", "weekly", "critical"]
},
"matches": [
{
"line": " command: pg_dump ${params.target_db} | gzip > backup_$(date +%Y%m%d).sql.gz",
"lineNumber": 25,
"startLine": 20
}
]
}
],
"errors": []
}Get Metrics for Monitoring
curl "http://localhost:8080/api/v1/metrics" | grep dagu_dag_runs_currently_runningStop a Running DAG
curl -X POST "http://localhost:8080/api/v1/dag-runs/data-pipeline/20240211_120000/stop" \
-H "Authorization: Bearer your-token"Update Step Status Manually
# Mark a failed step as successful
curl -X PATCH "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/20240211_020000_def456/steps/transform_data/status" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"status": 4}'Response (200): Success (empty response body)
Approve a Waiting Step
curl -X POST "http://localhost:8080/api/v1/dag-runs/deploy-pipeline/20240211_120000/steps/deploy-staging/approve" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"inputs": {"APPROVED_BY": "ops-team"}}'Reject a Waiting Step
curl -X POST "http://localhost:8080/api/v1/dag-runs/deploy-pipeline/20240211_120000/steps/deploy-staging/reject" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"reason": "Not ready for production"}'Push Back a Waiting Step
curl -X POST "http://localhost:8080/api/v1/dag-runs/report-pipeline/20240211_120000/steps/generate-report/push-back" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"inputs": {"FEEDBACK": "Include error counts"}}'Enqueue a DAG Run
curl -X POST "http://localhost:8080/api/v1/dags/ml-training-pipeline/enqueue" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{
"params": "{\"model\": \"recommendation_v3\", \"dataset\": \"user_interactions_2024\"}",
"dagRunId": "ml_train_20240211_170000",
"dagName": "ml-training-on-demand",
"queue": "gpu-jobs"
}'Response:
{
"dagRunId": "ml_train_20240211_170000"
}Get Logs with Pagination
# Get last 100 lines of a DAG run log
curl "http://localhost:8080/api/v1/dag-runs/etl-pipeline/20240211_120000/log?tail=100"
# Get specific step's stderr output
curl "http://localhost:8080/api/v1/dag-runs/etl-pipeline/20240211_120000/steps/transform/log?stream=stderr"
# Get logs with offset and limit
curl "http://localhost:8080/api/v1/dag-runs/etl-pipeline/20240211_120000/log?offset=1000&limit=500"Working with Sub DAGs
# Get all sub DAG runs with timing information
curl "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/20240211_140000_abc123/sub-dag-runs" \
-H "Authorization: Bearer your-token"
# Get sub DAG run details
curl "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/20240211_140000_abc123/sub-dag-runs/sub_20240211_143020_xyz456" \
-H "Authorization: Bearer your-token"
# Get sub DAG step log
curl "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/20240211_140000_abc123/sub-dag-runs/sub_20240211_143020_xyz456/steps/load_data/log" \
-H "Authorization: Bearer your-token"
# Update sub DAG step status
curl -X PATCH "http://localhost:8080/api/v1/dag-runs/data-processing-pipeline/20240211_140000_abc123/sub-dag-runs/sub_20240211_143020_xyz456/steps/load_data/status" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"status": 4}'Rename a DAG
curl -X POST "http://localhost:8080/api/v1/dags/old-pipeline-name/rename" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"newFileName": "new-pipeline-name"}'Response (200): Success (empty response body)
Delete a DAG
curl -X DELETE "http://localhost:8080/api/v1/dags/deprecated-pipeline" \
-H "Authorization: Bearer your-token"Response (204): No content (successful deletion)
Get DAG Specification YAML
curl "http://localhost:8080/api/v1/dags/data-processing-pipeline/spec" \
-H "Authorization: Bearer your-token"Response:
{
"dag": {
"name": "data_processing_pipeline",
"group": "ETL"
},
"spec": "group: ETL\nschedule:\n - \"0 2 * * *\"\n - \"0 14 * * *\"\ndescription: Daily data processing pipeline for warehouse ETL\nenv:\n DATA_SOURCE: postgres://prod-db:5432/analytics\nlog_dir: /var/log/dagu/pipelines\nhist_retention_days: 30\nmax_active_steps: 5\nparams:\n - name: date\n type: string\n required: true\n - name: environment\n type: string\n default: dev\n enum: [dev, staging, prod]\n - name: batch_size\n type: integer\n default: 1000\n minimum: 1\n maximum: 10000\nlabels: [production, etl, daily]\npreconditions:\n - eval: \"$(date +%u)\"\n expected: \"re:[1-5]\"\nsteps:\n - id: extract\n description: Extract data from source database\n working_dir: /app/etl\n run: python extract.py --date \"${params.date}\"\n stdout: /logs/extract.out\n stderr: /logs/extract.err\n\n - id: transform\n description: Apply transformations to extracted data\n run: python transform.py\n depends: [extract]\n\n - id: load\n run: warehouse-loader --batch-size \"${params.batch_size}\"\n depends: [transform]\nhandler_on:\n success:\n run: notify.sh \"Pipeline completed successfully\"\n failure:\n run: alert.sh \"Pipeline failed\" high\n exit:\n run: cleanup_temp_files.sh",
"errors": []
}Complex Filtering Examples
# Get all failed DAG runs in the last 24 hours
curl "http://localhost:8080/api/v1/dag-runs?status=2&fromDate=$(date -d '24 hours ago' +%s)" \
-H "Authorization: Bearer your-token"
# Get DAG runs for a specific DAG with cursor pagination
curl "http://localhost:8080/api/v1/dag-runs?name=data-processing-pipeline&limit=20" \
-H "Authorization: Bearer your-token"
# Search for DAGs with specific labels
curl "http://localhost:8080/api/v1/dags?labels=production&page=1&perPage=50" \
-H "Authorization: Bearer your-token"
# Get running DAG runs
curl "http://localhost:8080/api/v1/dag-runs?status=1" \
-H "Authorization: Bearer your-token"
# Get queued DAG runs
curl "http://localhost:8080/api/v1/dag-runs?status=5" \
-H "Authorization: Bearer your-token"
# View all execution queues with running and queued DAG runs
curl "http://localhost:8080/api/v1/queues" \
-H "Authorization: Bearer your-token"
# Load queued items from one queue
curl "http://localhost:8080/api/v1/queues/default/items?type=queued&page=1&perPage=20" \
-H "Authorization: Bearer your-token"Pause/Resume the Scheduler
# Pause every schedule
curl -X POST "http://localhost:8080/api/v1/services/scheduler/pause" \
-H "Authorization: Bearer $DAGU_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"paused": true, "reason": "database migration"}'
# Check the current state
curl "http://localhost:8080/api/v1/services/scheduler/pause" \
-H "Authorization: Bearer $DAGU_API_TOKEN"
# Resume
curl -X POST "http://localhost:8080/api/v1/services/scheduler/pause" \
-H "Authorization: Bearer $DAGU_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"paused": false}'Suspend/Resume DAG Scheduling
# Suspend a DAG
curl -X POST "http://localhost:8080/api/v1/dags/data-processing-pipeline/suspend" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"suspend": true}'
# Resume a DAG
curl -X POST "http://localhost:8080/api/v1/dags/data-processing-pipeline/suspend" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-token" \
-d '{"suspend": false}'Response (200): Success (empty response body)
API Response Status Codes Summary
| Status Code | Description | Common Scenarios |
|---|---|---|
| 200 | Success | Successful GET, POST, PUT, PATCH requests |
| 201 | Created | New DAG created successfully |
| 204 | No Content | Successful DELETE operation |
| 400 | Bad Request | Invalid parameters, malformed JSON, invalid DAG name format |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | Insufficient permissions (e.g., no write access) |
| 404 | Not Found | DAG, DAG run, or resource doesn't exist |
| 409 | Conflict | Resource already exists (e.g., DAG name conflict) |
| 500 | Internal Error | Server-side processing error |
| 503 | Service Unavailable | Server unhealthy or scheduler not responding |
API Key Endpoints
API key management endpoints require Builtin Authentication mode and admin role.
List API Keys
Endpoint: GET /api/v1/api-keys
Retrieves all API keys. Requires admin role.
Response (200):
{
"apiKeys": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ci-pipeline",
"description": "API key for CI/CD pipeline",
"role": "operator",
"keyPrefix": "dagu_7Kq",
"createdAt": "2024-02-11T12:00:00Z",
"updatedAt": "2024-02-11T12:00:00Z",
"createdBy": "admin-user-id",
"lastUsedAt": "2024-02-11T15:30:00Z"
}
]
}Create API Key
Endpoint: POST /api/v1/api-keys
Creates a new API key. Requires admin role.
Request Body:
{
"name": "ci-pipeline",
"description": "API key for CI/CD pipeline",
"role": "operator"
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| name | string | Human-readable name (1-100 chars) | Yes |
| description | string | Optional description (max 500 chars) | No |
| role | string | Role: "admin", "manager", "operator", "viewer" | Yes |
Response (201):
{
"apiKey": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ci-pipeline",
"description": "API key for CI/CD pipeline",
"role": "operator",
"keyPrefix": "dagu_7Kq",
"createdAt": "2024-02-11T12:00:00Z",
"updatedAt": "2024-02-11T12:00:00Z",
"createdBy": "admin-user-id"
},
"key": "dagu_7Kq9mXxN3pLwR5tY2vZa8bCdEfGhJk4n6sUwXy0zA1B"
}WARNING
The key field contains the full API key secret and is only returned once at creation time. Store it securely.
Error Response (409):
{
"code": "already_exists",
"message": "API key with this name already exists"
}Get API Key
Endpoint: GET /api/v1/api-keys/{keyId}
Retrieves a specific API key by ID. Requires admin role.
Response (200):
{
"apiKey": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "ci-pipeline",
"description": "API key for CI/CD pipeline",
"role": "operator",
"keyPrefix": "dagu_7Kq",
"createdAt": "2024-02-11T12:00:00Z",
"updatedAt": "2024-02-11T12:00:00Z",
"createdBy": "admin-user-id",
"lastUsedAt": "2024-02-11T15:30:00Z"
}
}Error Response (404):
{
"code": "not_found",
"message": "API key not found"
}Update API Key
Endpoint: PATCH /api/v1/api-keys/{keyId}
Updates an API key's metadata. Requires admin role.
Request Body:
{
"name": "production-ci",
"description": "Updated description",
"role": "manager"
}All fields are optional. Only provided fields are updated.
Response (200):
{
"apiKey": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "production-ci",
"description": "Updated description",
"role": "manager",
"keyPrefix": "dagu_7Kq",
"createdAt": "2024-02-11T12:00:00Z",
"updatedAt": "2024-02-11T16:00:00Z",
"createdBy": "admin-user-id",
"lastUsedAt": "2024-02-11T15:30:00Z"
}
}Error Response (409):
{
"code": "already_exists",
"message": "API key with this name already exists"
}Delete API Key
Endpoint: DELETE /api/v1/api-keys/{keyId}
Deletes (revokes) an API key. Requires admin role.
Response (204): No content
Error Response (404):
{
"code": "not_found",
"message": "API key not found"
}Example: Full API Key Workflow
# 1. Login as admin to get JWT token
TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin-password"}' | jq -r '.token')
# 2. Create an API key
API_KEY=$(curl -s -X POST http://localhost:8080/api/v1/api-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "automation", "role": "operator"}' | jq -r '.key')
echo "Created API key: $API_KEY"
# 3. Use the API key for subsequent requests
curl -H "Authorization: Bearer $API_KEY" \
http://localhost:8080/api/v1/dags
# 4. Start a DAG with the API key
curl -X POST http://localhost:8080/api/v1/dags/my-dag/start \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"params": "{\"env\": \"production\"}"}'Webhook Endpoints
Webhooks expose a DAG-specific trigger endpoint and a separate set of management endpoints.
- Trigger endpoint:
POST /api/v1/webhooks/{fileName} - Management endpoints: developer, manager, or admin only when builtin auth is enabled
- Per-DAG limit: one webhook configuration per DAG
Trigger DAG via Webhook
Endpoint: POST /api/v1/webhooks/{fileName}
The trigger endpoint uses the webhook's configured authentication mode instead of regular API authentication. New webhooks default to token-only authentication; other modes can require HMAC or both token and HMAC.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| fileName | string | DAG file name |
Request Headers:
| Header | Description |
|---|---|
| Authorization | Bearer dagu_wh_<token>. Accepts the webhook's default token or one of its profile tokens. Required when the webhook auth mode includes token authentication. |
| X-Dagu-Signature | HMAC-SHA256 signature as sha256=<hex>. Required when strict HMAC enforcement is active. |
| X-Dagu-Profile | Optional runtime profile for the run. With the default token, the profile must be in the webhook's profile-selection allowlist. With a profile token, the run always uses the token's profile, and the header may only name that profile. |
Request Body (optional):
{
"dagRunId": "deploy-abc123",
"payload": {
"branch": "main",
"commit": "abc123"
}
}Request Fields:
| Field | Type | Description | Required |
|---|---|---|---|
| dagRunId | string | Optional idempotency key. If a run with this ID already exists, the endpoint returns 409 Conflict. | No |
| payload | object | Arbitrary JSON exposed to the DAG as WEBHOOK_PAYLOAD. | No |
Payload behavior is slightly broader than the schema shape:
- If the request body includes a top-level
payloadfield, Dagu serializes that field intoWEBHOOK_PAYLOAD. - Otherwise, if the raw request body is valid JSON, Dagu serializes the entire raw body into
WEBHOOK_PAYLOAD. - Otherwise
WEBHOOK_PAYLOADis{}. - If you want a custom
dagRunIdwithout mixing it intoWEBHOOK_PAYLOAD, use the wrapper form shown above. - The maximum accepted request body is controlled by
webhooks.max_payload_sizeinconfig.yamland defaults to1048576bytes.
If X-Dagu-Profile is omitted, Dagu uses the DAG's normal default-profile resolution. When HMAC is enabled, sign the exact raw request body if the profile header is absent. If the header is present, sign:
x-dagu-profile:<profile>\n<raw-request-body>The prefix contains the lowercase literal x-dagu-profile, a colon, the selected profile, and one newline byte. The header and body are therefore both covered by the signature. See Select a Runtime Profile for a complete example.
Response (200):
{
"dagRunId": "0196808c-04ff-73bb-a63e-83791b321ac0",
"dagName": "my-dag"
}Error Response (401):
{
"code": "unauthorized",
"message": "missing or invalid authorization header"
}Another 401 response is invalid webhook token.
Missing or invalid HMAC signatures also return 401 when strict HMAC enforcement is active.
Error Response (403):
{
"code": "forbidden",
"message": "webhook is disabled"
}Error Response (404):
{
"code": "not_found",
"message": "DAG my-dag not found"
}Other 404 responses include no webhook configured for this DAG and webhook triggering is not configured on this server.
Profile-selection errors include:
400 Bad Requestfor an invalid or repeatedX-Dagu-Profileheader, or a disabled selected profile403 Forbiddenwhen the selected profile is not in the webhook allowlist, or a profile token was sent with a header naming a different profile404 Not Foundwhen an allowlisted profile no longer exists
Error Response (409):
{
"code": "already_exists",
"message": "dag-run with ID deploy-abc123 already exists"
}Error Response (413):
{
"code": "bad_request",
"message": "webhook payload too large (max 1048576 bytes)"
}Webhook Details Shape
Webhook management endpoints return the same public webhook object:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"dagName": "my-dag",
"tokenPrefix": "dagu_wh_7Kq9",
"enabled": true,
"authMode": "token_only",
"hmac": {
"enabled": false,
"secretConfigured": false
},
"profileSelection": {
"allowedProfiles": ["staging", "prod"]
},
"profileTokens": [
{
"id": "8f14e45f-ceea-467f-a8f4-1c2b3d4e5f60",
"name": "customer-a",
"tokenPrefix": "dagu_wh_Pq3R",
"profile": "customer-a",
"createdAt": "2026-04-29T10:30:00Z",
"createdBy": "user-id",
"lastUsedAt": "2026-04-29T10:45:00Z"
}
],
"createdAt": "2026-04-29T10:00:00Z",
"updatedAt": "2026-04-29T10:15:00Z",
"createdBy": "user-id",
"lastUsedAt": "2026-04-29T10:20:00Z",
"public": {
"sender": "github",
"secretConfigured": true,
"events": ["push", "pull_request.opened"],
"createdAt": "2026-04-29T11:00:00Z"
}
}Notes:
tokenPrefixis the stored identification prefix from the full token, currently the first 12 characters when available.profileSelection.allowedProfileslists the runtime profiles callers may select withX-Dagu-Profile. An empty list disables caller selection.profileTokenslists the webhook's profile tokens without their secrets.lastUsedAtis set once a token has authorized a request. Remote nodes running versions without profile tokens omit the field.lastUsedAton the webhook and on each profile token is updated at most once per minute, so it can lag the latest request by up to a minute.publicis present while the webhook has a public URL.senderis absent when anyone with the URL may send. The URL itself comes from Get Public URL.
List All Webhooks
Endpoint: GET /api/v1/webhooks
Lists all configured webhooks across DAGs. Requires developer, manager, or admin role.
Response (200):
{
"webhooks": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"dagName": "my-dag",
"tokenPrefix": "dagu_wh_7Kq9",
"enabled": true,
"createdAt": "2026-04-29T10:00:00Z",
"updatedAt": "2026-04-29T10:00:00Z",
"createdBy": "user-id",
"lastUsedAt": "2026-04-29T10:20:00Z"
}
]
}Get DAG Webhook
Endpoint: GET /api/v1/dags/{fileName}/webhook
Returns the webhook configuration for one DAG. Requires developer, manager, or admin role.
Error Response (404):
{
"code": "not_found",
"message": "no webhook configured for DAG my-dag"
}Create DAG Webhook
Endpoint: POST /api/v1/dags/{fileName}/webhook
Creates the webhook record and returns the full token once. Requires developer, manager, or admin role.
Response (201):
{
"webhook": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"dagName": "my-dag",
"tokenPrefix": "dagu_wh_7Kq9",
"enabled": true,
"createdAt": "2026-04-29T10:00:00Z",
"updatedAt": "2026-04-29T10:00:00Z",
"createdBy": "user-id"
},
"token": "dagu_wh_7Kq9mXxN3pLwR5tY2vZa8bCdEfGhJk4n6sUwXy0zA1B"
}The token field is returned only at creation and token-regeneration time.
Configure Webhook Profile Selection
Endpoint: PUT /api/v1/dags/{fileName}/webhook/profile-selection
Replaces the runtime profiles that callers may select with X-Dagu-Profile. This endpoint is admin-only.
Request Body:
{
"allowedProfiles": ["staging", "prod"]
}allowedProfiles is required. Each profile must exist and be active. Send [] to explicitly clear the policy and disable caller selection. Missing or null allowedProfiles values return 400 Bad Request.
Response (200): The updated webhook details.
Policy changes apply to subsequent trigger requests immediately; the server does not need to be restarted.
Error Responses:
400 Bad Request: invalid, disabled, or missing profile input404 Not Found: webhook or requested runtime profile not found
Create Webhook Profile Token
Endpoint: POST /api/v1/dags/{fileName}/webhook/profile-tokens
Creates an additional webhook token bound to one runtime profile and returns the full token once. Requests authenticated with it always run with that profile. This endpoint is admin-only.
Request Body:
{
"name": "customer-a",
"profile": "customer-a"
}| Field | Type | Description | Required |
|---|---|---|---|
| name | string | Label for the caller that holds the token, 1 to 100 characters. | Yes |
| profile | string | Runtime profile the token runs with. It must exist and be active. | Yes |
Response (201): The updated webhook details as webhook and the full token as token, in the same shape as Create DAG Webhook.
Error Responses:
400 Bad Request: invalid name, disabled profile, webhook auth mode ishmac_only, or the webhook already has 100 profile tokens404 Not Found: webhook or runtime profile not found
Revoke Webhook Profile Token
Endpoint: DELETE /api/v1/dags/{fileName}/webhook/profile-tokens/{tokenId}
Revokes a profile token. The token is rejected from the next request on. This endpoint is admin-only.
Response (200): The updated webhook details.
Error Responses:
404 Not Found: webhook or profile token not found
Regenerate Webhook Token
Endpoint: POST /api/v1/dags/{fileName}/webhook/regenerate
Rotates the webhook token and invalidates the old token immediately. Requires developer, manager, or admin role.
Response (200):
{
"webhook": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"dagName": "my-dag",
"tokenPrefix": "dagu_wh_9XzN",
"enabled": true,
"createdAt": "2026-04-29T10:00:00Z",
"updatedAt": "2026-04-29T10:30:00Z",
"createdBy": "user-id"
},
"token": "dagu_wh_9XzNewTokenGeneratedHereAfterRegeneration"
}Toggle Webhook
Endpoint: POST /api/v1/dags/{fileName}/webhook/toggle
Enables or disables the webhook without rotating credentials. Requires developer, manager, or admin role.
Request Body:
{
"enabled": false
}Response (200):
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"dagName": "my-dag",
"tokenPrefix": "dagu_wh_7Kq9",
"enabled": false,
"createdAt": "2026-04-29T10:00:00Z",
"updatedAt": "2026-04-29T10:30:00Z",
"createdBy": "user-id",
"lastUsedAt": "2026-04-29T10:20:00Z"
}Delete DAG Webhook
Endpoint: DELETE /api/v1/dags/{fileName}/webhook
Deletes the webhook configuration. Requires developer, manager, or admin role.
Response (204): No content
Example: Using Webhooks
# 1. Login with a user that has developer, manager, or admin role
TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "your-username", "password": "your-password"}' | jq -r '.token')
# 2. Create a webhook for a DAG
WEBHOOK=$(curl -s -X POST http://localhost:8080/api/v1/dags/my-dag/webhook \
-H "Authorization: Bearer $TOKEN")
WEBHOOK_TOKEN=$(echo $WEBHOOK | jq -r '.token')
# 3. Trigger the DAG using the webhook
curl -X POST http://localhost:8080/api/v1/webhooks/my-dag \
-H "Authorization: Bearer $WEBHOOK_TOKEN" \
-H "Content-Type: application/json" \
-d '{"payload": {"branch": "main", "commit": "abc123"}}'
# 4. Regenerate token if compromised
curl -X POST http://localhost:8080/api/v1/dags/my-dag/webhook/regenerate \
-H "Authorization: Bearer $TOKEN"
# 5. Disable webhook temporarily
curl -X POST http://localhost:8080/api/v1/dags/my-dag/webhook/toggle \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'Public URL Endpoints
A webhook can have a public URL through Dagu Cloud (Dagu v2.20 or later). These endpoints need builtin auth and a server connected to Dagu Console. Changes are admin-only and written to the audit log; developers and managers can read. All take the remoteNode query parameter.
Get Public URL
Endpoint: GET /api/v1/dags/{fileName}/webhook/public
Returns the public URL, whether Dagu Cloud holds it, the latest 20 deliveries, and missed counts. Returns 404 when the DAG has no webhook or no public URL.
Response (200):
{
"url": "https://hooks.dagu.sh/7f3c9a1e5b2d4c8e/q3Vt9dXbVQ7m2YkqRk4fJ8sWnP1aLc6tHe0uGz5yBxA",
"state": "active",
"maxHooks": 1000,
"deliveries": [
{
"id": "6b1f0c2e-1a5d-4c3e-9f7a-2d8b4e6c1a90",
"receivedAt": "2026-04-29T11:05:00Z",
"size": 7421,
"event": "push",
"outcome": "started",
"dagRunId": "6b1f0c2e-1a5d-4c3e-9f7a-2d8b4e6c1a90"
}
],
"missed": { "expired": 0, "refused": 0 }
}| Field | Description |
|---|---|
url | The public URL. Absent until Dagu Cloud has given the server its address. |
state | active once Dagu Cloud holds the URL, connecting before then, overLimit when the plan allows fewer public URLs than the server has and this one is left out, or error. |
error | Why the server cannot connect, when state is error. |
maxHooks | The most public URLs the server's plan allows, once known. |
deliveries[].outcome | started, waiting (Dagu Cloud sends it again), rejected (signature check failed), skipped (an event the webhook does not take), ignored (the webhook was disabled), or failed. |
deliveries[].error | Why a delivery was rejected, skipped, waiting, or failed. |
missed | Requests Dagu Cloud dropped since they were last dismissed: expired waited more than 7 days, refused arrived while 1,000 were waiting. since is when the first was counted. |
Turn On Public URL
Endpoint: POST /api/v1/dags/{fileName}/webhook/public
Gives the webhook a public URL. A webhook that has one keeps it. Admin only.
Response (200): the webhook, with public set.
Error Responses: 400 when the server is not connected to Dagu Cloud or the plan allows no more public URLs, 404 when the DAG has no webhook, 501 when encrypted storage is not available.
Turn Off Public URL
Endpoint: DELETE /api/v1/dags/{fileName}/webhook/public
Removes the public URL and its delivery list. Dagu Cloud drops the URL and every request waiting for it. Admin only.
Response (200): the webhook, without public.
Replace Public URL
Endpoint: POST /api/v1/dags/{fileName}/webhook/public/regenerate
Gives the webhook a new public URL. The old URL stops working; requests Dagu Cloud already took through it are still delivered. Admin only.
Response (200): the webhook.
Configure Public URL Sender
Endpoint: PUT /api/v1/dags/{fileName}/webhook/public/sender
Sets whose signature requests must carry, and which events start runs. Admin only.
Request Body:
{
"sender": "github",
"secret": "",
"generateSecret": false,
"events": ["push", "pull_request.opened"]
}| Field | Description |
|---|---|
sender | github, stripe, slack, or standard. Omit it to accept anyone with the URL. |
secret | The sender's signing secret. Empty keeps the stored secret of the same sender, or, for GitHub, makes one. |
generateSecret | Make a new GitHub signing secret. |
events | Events that start runs. Empty takes every event. A name also takes the events under it. |
Response (200):
{
"webhook": { "dagName": "my-dag", "public": { "sender": "github", "secretConfigured": true, "events": ["push"], "createdAt": "2026-04-29T11:00:00Z" } },
"secret": "4f9c2b7e0d1a8c3f5e6b9a2d4c7f1e0b8a3d6c9f2e5b8a1d4c7f0e3b6a9d2c5f"
}secret is present only when Dagu made one, and is shown only this once.
Error Responses: 400 for an unknown sender, a missing or malformed secret, or more than 50 events.
Dismiss Missed Deliveries
Endpoint: DELETE /api/v1/dags/{fileName}/webhook/public/missed
Clears the missed counts. Admin only.
Response (204): No content
Workers Endpoints
List Workers
Endpoint: GET /api/v1/workers
Retrieves information about connected workers in the distributed execution system.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"workers": [
{
"id": "worker-gpu-01",
"labels": {
"gpu": "true",
"cuda": "11.8",
"memory": "64G",
"region": "us-east-1"
},
"health_status": "WORKER_HEALTH_STATUS_HEALTHY",
"last_heartbeat": "2024-02-11T12:00:00Z",
"running_tasks": [
{
"dagName": "ml-training-pipeline",
"dagRunId": "20240211_120000_abc123",
"rootDagRunName": "ml-training-pipeline",
"rootDagRunId": "20240211_120000_abc123",
"parentDagRunName": "",
"parentDagRunId": "",
"startedAt": "2024-02-11T12:00:00Z"
}
]
},
{
"id": "worker-cpu-02",
"labels": {
"cpu-arch": "amd64",
"cpu-cores": "32",
"region": "us-east-1"
},
"health_status": "WORKER_HEALTH_STATUS_WARNING",
"last_heartbeat": "2024-02-11T11:59:50Z",
"running_tasks": []
},
{
"id": "worker-eu-01",
"labels": {
"region": "eu-west-1",
"compliance": "gdpr"
},
"health_status": "WORKER_HEALTH_STATUS_UNHEALTHY",
"last_heartbeat": "2024-02-11T11:59:30Z",
"running_tasks": [
{
"dagName": "data-processor",
"dagRunId": "20240211_113000_def456",
"rootDagRunName": "data-pipeline",
"rootDagRunId": "20240211_110000_xyz789",
"parentDagRunName": "data-pipeline",
"parentDagRunId": "20240211_110000_xyz789",
"startedAt": "2024-02-11T11:30:00Z"
}
]
}
]
}Worker Health Status Values:
WORKER_HEALTH_STATUS_HEALTHY: Last heartbeat < 5 seconds agoWORKER_HEALTH_STATUS_WARNING: Last heartbeat 5-15 seconds agoWORKER_HEALTH_STATUS_UNHEALTHY: Last heartbeat > 15 seconds ago
Running Task Fields:
dagName: Name of the DAG being executeddagRunId: ID of the current DAG runrootDagRunName: Name of the root DAG (for nested workflows)rootDagRunId: ID of the root DAG runparentDagRunName: Name of the immediate parent DAG (empty for root DAGs)parentDagRunId: ID of the immediate parent DAG runstartedAt: When the task execution started
Error Response (503) (when coordinator is not running):
{
"code": "service_unavailable",
"message": "Coordinator service is not available"
}Wiki Endpoints
Wiki endpoints accept optional workspace and remoteNode query parameters. Page paths are relative to the selected workspace and omit the .md extension. All authenticated users can list, read, and search pages they can access. Mutations require permissions.write_dags and a write-capable workspace role.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/wiki | List the page tree, or use flat=true for a flat list. |
POST | /api/v1/wiki | Create a Markdown page. |
GET | /api/v1/wiki/search?q=... | Search page content. |
GET | /api/v1/wiki/backlinks?path=... | List pages that link to a page. |
GET | /api/v1/wiki/page?path=... | Read one page. |
PATCH | /api/v1/wiki/page?path=... | Replace one page's content. |
DELETE | /api/v1/wiki/page?path=... | Delete one page. |
POST | /api/v1/wiki/page/rename?path=... | Rename or move a page or directory. |
GET | /api/v1/wiki/page/revisions?path=... | List saved revisions for one page. |
GET | /api/v1/wiki/page/revision?path=... | Read one saved revision. |
POST | /api/v1/wiki/page/attachment?path=... | Upload an attachment for a page. |
GET | /api/v1/wiki/page/attachment?path=... | Download a page attachment. |
POST | /api/v1/wiki/delete-batch | Delete several pages or directories. |
Create a page in the operations directory of the default workspace:
curl -X POST "http://localhost:8080/api/v1/wiki?workspace=default" \
-H "Content-Type: application/json" \
-d '{"id":"operations/runbook","content":"# Operations runbook\n"}'Read or update the same page:
curl "http://localhost:8080/api/v1/wiki/page?workspace=default&path=operations%2Frunbook"
curl -X PATCH "http://localhost:8080/api/v1/wiki/page?workspace=default&path=operations%2Frunbook" \
-H "Content-Type: application/json" \
-d '{"content":"# Updated operations runbook\n"}'Rename and batch-delete requests use newPath and paths respectively:
{"newPath":"operations/deploy"}{"paths":["operations/deploy","legacy"]}The old /api/v1/docs endpoints are not part of the current API. Upgrade direct clients to /api/v1/wiki; Dagu's remote-node proxy falls back to the legacy paths when it connects to an older Dagu node.
See Wiki for Web UI behavior, workspace ownership, storage, and Git Sync.
Calendar Endpoints
Calendar endpoints manage the business calendar files in paths.calendars_dir and accept an optional remoteNode query parameter. All authenticated users can list, read, and preview calendars. Creating, updating, and deleting require permissions.write_dags and, where roles apply, the developer role or above; they are refused while Git Sync runs with push_enabled: false.
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1/calendars | List the built-in calendars and calendar files. includeUsage=true adds expiry warnings, includes, and how many DAGs use each calendar. |
POST | /api/v1/calendars | Create a calendar. Without spec, the file starts as Monday to Friday with no holidays, listed through the end of the year. |
GET | /api/v1/calendars/{name} | Read a calendar's content, its problems with positions, and the calendars and DAGs that depend on it. |
PUT | /api/v1/calendars/{name} | Replace a calendar's content. Content with problems is not saved; the problems are returned. |
DELETE | /api/v1/calendars/{name} | Delete a calendar. Returns 409 while a DAG's start schedule uses it, directly or through includes, or another calendar includes it. |
POST | /api/v1/calendars/{name}/preview | Apply unsaved content to the dates of a year, marking each date's status and the dates that differ from the saved calendar. |
Create a calendar, then add a holiday:
curl -X POST "http://localhost:8080/api/v1/calendars" \
-H "Content-Type: application/json" \
-d '{"name":"jp-banking"}'
curl -X PUT "http://localhost:8080/api/v1/calendars/jp-banking" \
-H "Content-Type: application/json" \
-d '{"spec":"holidays:\n - date: 2027-01-11\n name: Coming of Age Day\n"}'Preview unsaved content for 2027:
{"spec":"holidays: [2027-01-11]\n","year":2027}The usage counts and dependents list only DAGs in workspaces the caller can see.
Git Sync Endpoints
Git Sync manages workflow definitions, Wiki content, calendars, and Git-tracked supporting files. The kind field is one of dag, doc, doc-asset, file, or calendar.
DAG and Wiki page IDs omit their file extension. Supporting-file and attachment IDs retain their complete relative path and file extension. URL-encode IDs in endpoint paths: scripts/report.py becomes scripts%2Freport.py, while assets/100%.txt becomes assets%2F100%25.txt.
All sync endpoints accept an optional remoteNode query parameter.
Get Sync Status
Endpoint: GET /api/v1/sync/status
Returns overall sync status, item list, and status counts.
curl "http://localhost:8080/api/v1/sync/status"Response (200):
{
"enabled": true,
"repository": "github.com/your-org/dags",
"branch": "main",
"summary": "pending",
"lastSyncAt": "2026-02-26T10:00:00Z",
"lastSyncCommit": "abc123",
"items": [
{
"itemId": "my-dag",
"filePath": "my-dag.yaml",
"displayName": "my-dag.yaml",
"kind": "dag",
"status": "modified"
},
{
"itemId": "scripts/report.py",
"filePath": "scripts/report.py",
"displayName": "scripts/report.py",
"kind": "file",
"status": "synced"
}
],
"counts": {
"synced": 1,
"modified": 1,
"untracked": 0,
"conflict": 0,
"missing": 0
}
}Pull from Remote
Endpoint: POST /api/v1/sync/pull
Fetches and applies changes from the remote repository. Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/pull"Response (200):
{
"success": true,
"message": "Synced 2 and deleted 1 sync item(s)",
"synced": ["dag-a", "scripts/report.py"],
"deleted": ["scripts/obsolete.py"],
"modified": [],
"conflicts": [],
"errors": [],
"timestamp": "2026-02-26T10:00:00Z"
}Publish All
Endpoint: POST /api/v1/sync/publish-all
Publishes specified items, or all modified/untracked items if itemIds is omitted. Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/publish-all" \
-H "Content-Type: application/json" \
-d '{"message":"Batch publish","itemIds":["my-dag","scripts/report.py"]}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | No | Commit message |
itemIds | string[] | No | Item IDs to publish. Omit to publish all modified/untracked. |
Response (200): Same shape as Pull response.
Test Connection
Endpoint: POST /api/v1/sync/test-connection
Tests repository access and authentication.
curl -X POST "http://localhost:8080/api/v1/sync/test-connection"Response (200):
{
"success": true,
"message": "Connection successful"
}Get Config
Endpoint: GET /api/v1/sync/config
curl "http://localhost:8080/api/v1/sync/config"Response (200):
{
"enabled": true,
"repository": "github.com/your-org/dags",
"branch": "main",
"path": "",
"pushEnabled": true,
"auth": {
"type": "token"
},
"autoSync": {
"enabled": true,
"onStartup": true,
"interval": 300
},
"commit": {
"authorName": "Dagu",
"authorEmail": "dagu@localhost"
}
}Update Config
Endpoint: PUT /api/v1/sync/config
Admin only.
curl -X PUT "http://localhost:8080/api/v1/sync/config" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"repository": "github.com/your-org/dags",
"branch": "main",
"pushEnabled": true,
"auth": {"type": "token", "token": "ghp_xxx"},
"autoSync": {"enabled": true, "onStartup": true, "interval": 300},
"commit": {"authorName": "Dagu", "authorEmail": "dagu@localhost"}
}'Get Item Diff
Endpoint: GET /api/v1/sync/items/{itemId}/diff
Returns local and remote content for comparison. For binary files, the endpoint reports byte sizes instead of file content. Supporting-file responses can also indicate executable permissions and remote deletion status. Requires the sync service to be configured.
curl "http://localhost:8080/api/v1/sync/items/scripts%2Freport.py/diff"Response (200):
{
"itemId": "scripts/report.py",
"filePath": "scripts/report.py",
"kind": "file",
"status": "modified",
"localContent": "print('local')\n",
"remoteContent": "print('remote')\n",
"localExecutable": true,
"remoteExecutable": true
}For a binary supporting file, binary is true, localSize and remoteSize contain byte counts, and content fields are omitted. A deletion conflict sets remoteDeleted to true and omits remote content.
Response (404): Item not found.
Publish One Item
Endpoint: POST /api/v1/sync/items/{itemId}/publish
Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/items/my-dag/publish" \
-H "Content-Type: application/json" \
-d '{"message":"Update dag","force":false}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | No | Commit message |
force | boolean | No | Force publish over conflict |
Response (200): Same shape as Pull response.
Response (404): Item not found.
Response (409): Conflict detected (when force is false):
{
"itemId": "my-dag",
"remoteCommit": "abc123",
"remoteAuthor": "alice",
"remoteMessage": "Remote update",
"message": "conflict: \"my-dag\": remote has been updated"
}Discard Item Changes
Endpoint: POST /api/v1/sync/items/{itemId}/discard
Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/items/my-dag/discard"Response (200):
{
"message": "Discarded changes"
}Response (404): Item not found.
Forget Item
Endpoint: POST /api/v1/sync/items/{itemId}/forget
Removes the state entry for a missing, untracked, or conflict item. Rejects synced and modified items. Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/items/missing-dag/forget"Response (200):
{
"message": "Forgotten"
}Response (400): Item cannot be forgotten (synced or modified).
Response (404): Item not found.
Delete Item
Endpoint: POST /api/v1/sync/items/{itemId}/delete
Removes item from remote repository (git rm + commit + push), local disk, and sync state. Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/items/my-dag/delete" \
-H "Content-Type: application/json" \
-d '{"message":"Remove old DAG","force":false}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | No | Commit message |
force | boolean | No | Force delete when item has local modifications |
Response (200):
{
"message": "Deleted item: my-dag"
}Response (400): Item cannot be deleted (untracked, or modified without force, or push disabled).
Response (404): Item not found.
Delete All Missing Items
Endpoint: POST /api/v1/sync/delete-missing
Removes all missing items from remote repository, local disk, and sync state. Requires permissions.write_dags. Request body is optional.
curl -X POST "http://localhost:8080/api/v1/sync/delete-missing" \
-H "Content-Type: application/json" \
-d '{"message":"Clean up missing items"}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | No | Commit message |
Response (200):
{
"deleted": ["missing-a", "missing-b"],
"message": "Deleted 2 missing item(s)"
}Response (400): Push is disabled.
Move Item
Endpoint: POST /api/v1/sync/items/{itemId}/move
Renames a tracked item locally and in the remote repository. Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/items/old-dag/move" \
-H "Content-Type: application/json" \
-d '{"newItemId":"new-dag","message":"Rename workflow"}'Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
newItemId | string | Yes | New item ID |
message | string | No | Commit message |
force | boolean | No | Force move over conflict |
Response (200):
{
"message": "Moved old-dag to new-dag"
}Response (400): Validation error (cross-kind move, push disabled, invalid ID).
Response (404): Source item not found.
Response (409): Conflict detected (when force is false). Same shape as publish conflict response.
Cleanup
Endpoint: POST /api/v1/sync/cleanup
Removes all missing entries from sync state. Does not touch files on disk or remote. Requires permissions.write_dags.
curl -X POST "http://localhost:8080/api/v1/sync/cleanup"Response (200):
{
"forgotten": ["missing-a", "missing-b"],
"message": "Cleaned up 2 item(s)"
}API Versioning
- Current version: v2
- Legacy v1 endpoints are deprecated but still available
- Version is included in the URL path:
/api/v1/ - Breaking changes will result in a new API version
Workspace Endpoints
Workspace-aware list and search APIs use one optional workspace query parameter:
| Value | Meaning |
|---|---|
all | All resources the current user or API key can access. |
default | Resources without a valid workspace=<name> label. |
<workspace> | One named workspace. |
Mutation APIs use only default or a named workspace; all is an aggregate read value.
List Workspaces
Endpoint: GET /api/v1/workspaces
Retrieves workspaces. Requires authentication.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"workspaces": [
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "production",
"description": "Production workflows",
"createdAt": "2026-03-06T10:00:00Z",
"updatedAt": "2026-03-06T10:00:00Z"
}
]
}Returns an empty array if no workspaces exist.
Create Workspace
Endpoint: POST /api/v1/workspaces
Creates a new workspace. Requires developer role or above.
Request Body:
{
"name": "staging",
"description": "Optional description"
}name is required and must match ^[A-Za-z0-9_-]+$. description is optional.
Response (201): The created workspace object.
Error Response (400):
{
"code": "bad_request",
"message": "Name is required"
}Error Response (409):
{
"code": "already_exists",
"message": "Workspace with this name already exists"
}Get Workspace
Endpoint: GET /api/v1/workspaces/{workspaceId}
Retrieves a single workspace by ID. Requires authentication.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| workspaceId | string (UUID) | Workspace ID |
Response (200): The workspace object.
Error Response (404): Workspace not found.
Update Workspace
Endpoint: PATCH /api/v1/workspaces/{workspaceId}
Updates a workspace. Requires developer role or above. Uses PATCH semantics: only provided fields are updated.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| workspaceId | string (UUID) | Workspace ID |
Request Body:
{
"name": "new-name",
"description": "Updated description"
}Both fields are optional. Empty string for name is ignored.
Response (200): The updated workspace object.
Error Response (404): Workspace not found.
Error Response (409):
{
"code": "already_exists",
"message": "Workspace with this name already exists"
}Delete Workspace
Endpoint: DELETE /api/v1/workspaces/{workspaceId}
Deletes a workspace. Requires developer role or above.
Deleting a workspace removes the workspace record only. It does not rewrite DAG files, historical DAG runs, users, or API keys.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
| workspaceId | string (UUID) | Workspace ID |
Response (204): No content.
Error Response (404): Workspace not found.
Workspace Response Object
All endpoints returning workspace data use this shape:
| Field | Type | Description |
|---|---|---|
id | string | UUID |
name | string | Workspace name |
description | string | Optional description |
createdAt | string (date-time) | UTC creation timestamp |
updatedAt | string (date-time) | UTC last-update timestamp |
List Workspace Settings That v3.0 Moves into Projects
Endpoint: GET /api/v1/workspaces/deprecation
Lists the workspace settings in use that v3.0 moves into projects. Requires admin role.
Query Parameters:
| Parameter | Type | Description | Default |
|---|---|---|---|
| remoteNode | string | Remote node name | "local" |
Response (200):
{
"items": [
{ "kind": "baseConfig", "name": "billing" },
{ "kind": "secret", "name": "database/password (workspace billing)" },
{ "kind": "configKey", "name": "auth.oidc.role_mapping.workspace_mappings" },
{ "kind": "user", "name": "alice" }
]
}Items are ordered by kind, then name. kind is one of baseConfig, secret, profileEntry, notificationRoute, incidentPolicy, configKey, user, apiKey, or wikiDirectory. name identifies the setting, such as a workspace, a secret and its workspace, or a username. Returns an empty array when no workspace settings are in use.
See Workspaces for when each kind is reported.
Remote Node Support
Most endpoints support the remoteNode query parameter for multi-environment setups:
# Query a remote node
curl "http://localhost:8080/api/v1/dags?remoteNode=production" \
-H "Authorization: Bearer your-token"Remote nodes are configured in the server configuration file and allow managing DAGs across multiple Dagu instances from a single interface.

