Dagy API Reference
Complete API endpoint documentation for the Dagy platform. All endpoints require authentication via Bearer token or API key unless otherwise noted.
Base URL: https://api.dagy.io/v1
Table of Contents
- Authentication
- User
- Flows
- Deployments
- Runs
- Scheduling
- API Keys
- Organizations & Team
- DAG Drafts (Visual Builder)
- Usage & Billing
- Audit Logs
- Secrets Management
- Notifications & Alerts
- Environments
- Sensors
- Artifacts
- UI Config
- AI Studio
- Super Admin
- Node Registry
- Health & Monitoring
- Common Patterns
Authentication
POST /auth/login
Create an access token for API authentication.
Permissions: None required
Request Body:
{
"email": "user@example.com"
}
Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_at": "2026-03-02T12:00:00Z"
}
Status Codes:
200- Token created successfully400- Invalid email format401- Email not found or unauthorized
Example:
curl -X POST https://api.dagy.io/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com"}'
POST /auth/logout
Invalidate the current access token.
Permissions: Authenticated user
Headers:
Authorization: Bearer {token}
Response (204): No content
Status Codes:
204- Successfully logged out401- Invalid or missing token
Example:
curl -X POST https://api.dagy.io/v1/auth/logout \
-H "Authorization: Bearer {token}"
User
GET /me
Get basic identity information for the authenticated user.
Permissions: Authenticated user
Response (200):
{
"user_email": "user@example.com",
"org_id": "org_456def",
"role": "developer"
}
Status Codes:
200- Success401- Unauthorized
Example:
curl -X GET https://api.dagy.io/v1/me \
-H "Authorization: Bearer {token}"
GET /users/me
Get detailed profile information for the authenticated user, including organization name and super admin status.
Permissions: Authenticated user
Response (200):
{
"user_email": "user@example.com",
"super_admin": false,
"org_id": "org_456def",
"org_name": "Acme Corporation",
"role": "developer"
}
Status Codes:
200- Success401- Unauthorized
Example:
curl -X GET https://api.dagy.io/v1/users/me \
-H "Authorization: Bearer {token}"
Flows
POST /flows
Register a new flow version.
Permissions: flows.write
Request Body:
{
"flow_spec": {
"nodes": [...],
"edges": [...]
},
"artifact_s3_uri": "s3://dagy-artifacts/flows/my-flow-v1.zip",
"deployment_name": "my-flow-prod",
"schedule": "0 9 * * *",
"status": "active",
"default_executor": "local",
"namespace": "data/ingestion",
"tags": {"team": "data-eng", "env": "prod"}
}
Response (201):
{
"flow_name": "my-flow",
"flow_version": "1.0.0",
"namespace": "data/ingestion",
"tags": {"team": "data-eng", "env": "prod"},
"created_at": "2026-03-02T10:30:00Z",
"status": "active"
}
Status Codes:
201- Flow registered successfully400- Invalid flow specification401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/flows \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"flow_spec": {...},
"artifact_s3_uri": "s3://dagy-artifacts/flows/my-flow-v1.zip",
"deployment_name": "my-flow-prod",
"namespace": "data/ingestion",
"tags": {"team": "data-eng", "env": "prod"}
}'
GET /flows
List all flows with pagination support.
Permissions: flows.read
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Max results per page (default: 20, max: 100) |
next_token | string | Pagination token from previous response |
Response (200):
{
"items": [
{
"flow_name": "my-flow",
"flow_version": "1.0.0",
"namespace": "data/ingestion",
"tags": {"team": "data-eng", "env": "prod"},
"status": "active",
"created_at": "2026-02-15T08:00:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
],
"next_token": "eyJvZmZzZXQiOiAyMH0="
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/flows?limit=20" \
-H "Authorization: Bearer {token}"
POST /deployments
Create or update a deployment.
Permissions: flows.write
Request Body:
{
"name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": 5,
"schedule": "0 9 * * *",
"default_executor": "kubernetes",
"execution_mode": "nano",
"tags": ["production", "critical"]
}
Response (201):
{
"deployment_id": "deploy_456def",
"name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": 5,
"status": "active",
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Deployment created200- Deployment updated400- Invalid deployment configuration401- Unauthorized403- Insufficient permissions404- Flow not found
Example:
curl -X POST https://api.dagy.io/v1/deployments \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": 5,
"default_executor": "kubernetes"
}'
GET /backends
List available execution backends and their capabilities.
Permissions: flows.read
Response (200):
[
{
"name": "local",
"capabilities": ["cpu", "memory_limited", "short_running"],
"max_concurrent_runs": 10,
"max_timeout_seconds": 3600
},
{
"name": "kubernetes",
"capabilities": ["cpu", "gpu", "memory_unlimited", "long_running"],
"max_concurrent_runs": 1000,
"max_timeout_seconds": 86400
},
{
"name": "lambda",
"capabilities": ["cpu", "memory_configurable", "short_running", "serverless"],
"max_concurrent_runs": 1000,
"max_timeout_seconds": 900
}
]
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/backends \
-H "Authorization: Bearer {token}"
GET /flows/{flow_name}/latest
Get the latest version of a flow with its full specification.
Permissions: flows.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
flow_name | string | The flow name |
Response (200):
{
"flow_name": "my-flow",
"flow_version": "3",
"artifact_s3_uri": "s3://dagy-artifacts/flows/my-flow/3/artifact.zip",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z",
"schedule": "0 9 * * *",
"status": "active",
"deployment_name": "my-flow-prod",
"default_executor": "lambda",
"tags": {"team": "data-eng"},
"namespace": "data/ingestion",
"deployment_version": 3,
"code_hash": "a1b2c3d4e5f6...",
"flow_spec": {...}
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Flow not found
Example:
curl -X GET https://api.dagy.io/v1/flows/my-flow/latest \
-H "Authorization: Bearer {token}"
GET /flows/{flow_name}/versions
List all versions of a flow, sorted by deployment version descending.
Permissions: flows.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
flow_name | string | The flow name |
Response (200):
{
"flow_name": "my-flow",
"items": [
{
"flow_version": "3",
"deployment_version": 3,
"artifact_s3_uri": "s3://dagy-artifacts/flows/my-flow/3/artifact.zip",
"status": "active",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z"
},
{
"flow_version": "2",
"deployment_version": 2,
"status": "inactive",
"created_at": "2026-02-28T08:00:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/flows/my-flow/versions \
-H "Authorization: Bearer {token}"
GET /flows/{flow_name}/{flow_version}
Get a specific version of a flow with its full specification.
Permissions: flows.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
flow_name | string | The flow name |
flow_version | string | The flow version |
Response (200):
{
"flow_name": "my-flow",
"flow_version": "2",
"artifact_s3_uri": "s3://dagy-artifacts/flows/my-flow/2/artifact.zip",
"created_at": "2026-02-28T08:00:00Z",
"status": "inactive",
"deployment_name": "my-flow-prod",
"default_executor": "lambda",
"tags": {"team": "data-eng"},
"namespace": "data/ingestion",
"deployment_version": 2,
"flow_spec": {...}
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Flow version not found
Example:
curl -X GET https://api.dagy.io/v1/flows/my-flow/2 \
-H "Authorization: Bearer {token}"
DELETE /flows/{flow_name}
Delete a flow and all its versions. Cascades to runs, schedules, deployments, S3 artifacts, and CloudWatch logs.
Permissions: flows.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
flow_name | string | The flow name |
Response (200):
{
"status": "deleted",
"flow_name": "my-flow"
}
Status Codes:
200- Flow deleted401- Unauthorized403- Insufficient permissions
Example:
curl -X DELETE https://api.dagy.io/v1/flows/my-flow \
-H "Authorization: Bearer {token}"
Deployments
GET /deployments
List all deployments, optionally filtered by environment.
Permissions: flows.read
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
environment | string | Filter by environment name |
limit | integer | Max results (default: 100, max: 500) |
Response (200):
{
"items": [
{
"deployment_name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "3",
"environment": "production",
"schedule": "0 9 * * *",
"default_executor": "lambda",
"execution_mode": "nano",
"tags": {"team": "data-eng"},
"dep_package_slugs": ["pandas-layer"],
"updated_at": "2026-03-02T10:30:00Z",
"promoted_from_env": "staging",
"promoted_from_deployment": "my-flow-staging",
"promoted_at": "2026-03-01T14:00:00Z"
}
]
}
Status Codes:
200- Success400- Invalid limit401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/deployments?environment=production&limit=50" \
-H "Authorization: Bearer {token}"
POST /deployments/{deployment_name}/rollback
Roll back a deployment to a previous flow version.
Permissions: flows.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
deployment_name | string | The deployment name |
Request Body:
{
"target_flow_version": "2"
}
Response (200):
{
"deployment_name": "my-flow-prod",
"previous_flow_version": "3",
"new_flow_version": "2",
"rolled_back_at": "2026-03-02T11:00:00Z"
}
Status Codes:
200- Rollback successful401- Unauthorized403- Insufficient permissions404- Deployment not found
Example:
curl -X POST https://api.dagy.io/v1/deployments/my-flow-prod/rollback \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"target_flow_version": "2"}'
PATCH /deployments/{deployment_name}
Change the active flow version of a deployment.
Permissions: flows.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
deployment_name | string | The deployment name |
Request Body:
{
"flow_version": "4"
}
Response (200):
{
"deployment_name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "4",
"environment": "production",
"schedule": "0 9 * * *",
"default_executor": "lambda",
"tags": {"team": "data-eng"},
"updated_at": "2026-03-02T11:00:00Z"
}
Status Codes:
200- Version changed401- Unauthorized403- Insufficient permissions404- Deployment not found
Example:
curl -X PATCH https://api.dagy.io/v1/deployments/my-flow-prod \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"flow_version": "4"}'
GET /deployments/{deployment_name}
Get details of a single deployment including execution mode and dependency packages.
Permissions: flows.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
deployment_name | string | The deployment name |
Response (200):
{
"deployment_name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "5",
"environment": "production",
"schedule": "0 9 * * 1-5",
"default_executor": "lambda",
"execution_mode": "nano",
"tags": {"team": "data-eng"},
"dep_package_slugs": ["pandas-layer", "numpy-utils"],
"updated_at": "2026-03-12T10:00:00Z"
}
Status Codes:
200- Deployment found401- Unauthorized403- Insufficient permissions404- Deployment not found
Example:
curl -X GET https://api.dagy.io/v1/deployments/my-flow-prod \
-H "Authorization: Bearer {token}"
PUT /deployments/{deployment_name}/settings
Update mutable runtime settings on an existing deployment. Only the fields provided in the request body are updated; omitted fields are left unchanged.
Permissions: flows.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
deployment_name | string | The deployment name |
Request Body (all fields optional):
{
"execution_mode": "micro",
"default_executor": "flow-executor",
"schedule": "0 9 * * 1-5",
"tags": {"team": "data-eng", "priority": "high"},
"dep_package_slugs": ["pandas-layer", "numpy-utils"]
}
| Field | Type | Description |
|---|---|---|
execution_mode | string | Runtime tier: "nano", "micro", "small", "medium", "large", or "xlarge". Determines runtime tier and compute resources. |
default_executor | string | Backend override: "lambda", "flow-executor", "step-functions", "ecs", or "" (auto). |
schedule | string | Cron expression (5 fields) or interval (e.g. "30m"). Empty string removes the schedule. |
tags | object | Key-value tags. Replaces all existing tags. |
dep_package_slugs | string[] | Dependency package slugs to attach at runtime. Replaces existing list. |
Response (200):
{
"deployment_name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "5",
"environment": "production",
"schedule": "0 9 * * 1-5",
"default_executor": "flow-executor",
"execution_mode": "micro",
"tags": {"team": "data-eng", "priority": "high"},
"dep_package_slugs": ["pandas-layer", "numpy-utils"],
"updated_at": "2026-03-12T12:00:00Z"
}
Status Codes:
200- Settings updated400- No settings fields provided, or invalidexecution_mode401- Unauthorized403- Insufficient permissions404- Deployment not found
Example:
curl -X PUT https://api.dagy.io/v1/deployments/my-flow-prod/settings \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"execution_mode": "micro", "schedule": "0 9 * * 1-5"}'
DELETE /deployments/{deployment_name}
Delete a deployment.
Permissions: flows.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
deployment_name | string | The deployment name |
Response (200):
{
"status": "deleted",
"deployment_name": "my-flow-prod"
}
Status Codes:
200- Deployment deleted401- Unauthorized403- Insufficient permissions404- Deployment not found
Example:
curl -X DELETE https://api.dagy.io/v1/deployments/my-flow-prod \
-H "Authorization: Bearer {token}"
Runs
POST /runs
Trigger a new run.
Permissions: runs.trigger
Request Body:
{
"flow_name": "my-flow",
"flow_version": 5,
"parameters": {
"input_file": "s3://my-bucket/data.csv",
"threshold": 0.8
},
"execute": true,
"executor": "kubernetes",
"execution_mode": "micro"
}
Alternative: Use deployment instead of flow_name + flow_version
Response (202):
{
"run_id": "run_789ghi",
"run_slug": "my-flow-2026-03-02-101530",
"flow_name": "my-flow",
"flow_version": 5,
"status": "queued",
"created_at": "2026-03-02T10:15:30Z",
"parameters": {...}
}
Status Codes:
202- Run accepted and queued400- Invalid parameters or flow version401- Unauthorized403- Insufficient permissions404- Flow or deployment not found429- Quota exceeded or rate limit hit
Error Response (429):
{
"error": "quota_exceeded",
"message": "Monthly run quota exhausted",
"limit": 1000,
"current_usage": 1000,
"reset_at": "2026-04-02T00:00:00Z"
}
Example:
curl -X POST https://api.dagy.io/v1/runs \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"flow_name": "my-flow",
"flow_version": 5,
"parameters": {"input_file": "s3://my-bucket/data.csv"},
"execute": true
}'
GET /runs
List all runs for the authenticated user.
Permissions: runs.read
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
flow_name | string | Filter by flow name |
status | string | Filter by status (queued, running, completed, failed) |
limit | integer | Max results (default: 20, max: 100) |
next_token | string | Pagination token |
Response (200):
{
"items": [
{
"run_id": "run_789ghi",
"run_slug": "my-flow-2026-03-02-101530",
"flow_name": "my-flow",
"flow_version": 5,
"status": "completed",
"created_at": "2026-03-02T10:15:30Z",
"started_at": "2026-03-02T10:15:35Z",
"completed_at": "2026-03-02T10:25:45Z",
"duration_seconds": 610
}
],
"next_token": null
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/runs?flow_name=my-flow&status=completed&limit=50" \
-H "Authorization: Bearer {token}"
GET /runs/{run_id}
Get detailed information about a specific run.
Permissions: runs.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
run_id | string | The unique run identifier |
Response (200):
{
"run_id": "run_789ghi",
"run_slug": "my-flow-2026-03-02-101530",
"flow_name": "my-flow",
"flow_version": 5,
"deployment_name": "my-flow-prod",
"status": "completed",
"created_at": "2026-03-02T10:15:30Z",
"started_at": "2026-03-02T10:15:35Z",
"completed_at": "2026-03-02T10:25:45Z",
"duration_seconds": 610,
"executor": "flow-executor",
"execution_mode": "nano",
"parameters": {
"input_file": "s3://my-bucket/data.csv",
"threshold": 0.8
},
"output": {
"result_file": "s3://my-bucket/results.json",
"processed_records": 50000,
"status": "success"
},
"error": null,
"retry_count": 0,
"tags": ["manual_run"]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Run not found
Example:
curl -X GET https://api.dagy.io/v1/runs/run_789ghi \
-H "Authorization: Bearer {token}"
POST /runs/{run_id}/cancel
Cancel a running or queued run.
Permissions: runs.cancel
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
run_id | string | The unique run identifier |
Response (200):
{
"run_id": "run_789ghi",
"status": "cancelled",
"cancelled_at": "2026-03-02T10:20:00Z"
}
Status Codes:
200- Run cancelled successfully400- Run cannot be cancelled (already completed or failed)401- Unauthorized403- Insufficient permissions404- Run not found
Example:
curl -X POST https://api.dagy.io/v1/runs/run_789ghi/cancel \
-H "Authorization: Bearer {token}"
GET /runs/{run_id}/logs
Fetch CloudWatch execution logs for a run.
Permissions: runs.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
run_id | string | The unique run identifier |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
next_token | string | Pagination token for next page of logs |
limit | integer | Max log entries to return (default: 500) |
Response (200):
{
"events": [
{
"timestamp": "2026-03-02T10:15:35Z",
"message": "[INFO] Starting task: extract_data",
"ingestion_time": "2026-03-02T10:15:36Z"
}
],
"next_token": "f/1234567890..."
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Run not found
Example:
curl -X GET "https://api.dagy.io/v1/runs/run_789ghi/logs?limit=100" \
-H "Authorization: Bearer {token}"
Scheduling
POST /schedules
Create a new schedule for automated flow execution.
Permissions: schedules.write
Request Body:
{
"schedule_id": "daily-etl",
"flow_name": "my-flow",
"flow_version": 5,
"mode": "cron",
"enabled": true,
"timezone": "America/New_York",
"catchup_policy": "skip",
"cron_expression": "0 9 * * *",
"parameters": {
"environment": "prod",
"force_refresh": false
}
}
Mode-Specific Fields:
cron: Requirescron_expression(5-field format)interval: Requiresinterval_secondsone_time: Requiresstart_atmanual: No schedule fields required
Response (201):
{
"schedule_id": "daily-etl",
"flow_name": "my-flow",
"flow_version": 5,
"mode": "cron",
"enabled": true,
"timezone": "America/New_York",
"cron_expression": "0 9 * * *",
"created_at": "2026-03-02T10:30:00Z",
"next_run_at": "2026-03-03T09:00:00Z"
}
Status Codes:
201- Schedule created400- Invalid schedule configuration or cron expression401- Unauthorized403- Insufficient permissions404- Flow not found
Example:
curl -X POST https://api.dagy.io/v1/schedules \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"schedule_id": "daily-etl",
"flow_name": "my-flow",
"flow_version": 5,
"mode": "cron",
"enabled": true,
"timezone": "America/New_York",
"cron_expression": "0 9 * * *"
}'
GET /schedules
List all schedules.
Permissions: schedules.read
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
flow_name | string | Filter by flow name |
mode | string | Filter by mode (cron, interval, one_time, manual) |
enabled | boolean | Filter by enabled status |
limit | integer | Max results (default: 20, max: 100) |
Response (200):
{
"items": [
{
"schedule_id": "daily-etl",
"flow_name": "my-flow",
"flow_version": 5,
"mode": "cron",
"enabled": true,
"cron_expression": "0 9 * * *",
"timezone": "America/New_York",
"next_run_at": "2026-03-03T09:00:00Z",
"created_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/schedules?flow_name=my-flow&enabled=true" \
-H "Authorization: Bearer {token}"
GET /schedules/{schedule_id}
Get details of a specific schedule.
Permissions: schedules.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
schedule_id | string | The unique schedule identifier |
Response (200):
{
"schedule_id": "daily-etl",
"flow_name": "my-flow",
"flow_version": 5,
"mode": "cron",
"enabled": true,
"timezone": "America/New_York",
"catchup_policy": "skip",
"cron_expression": "0 9 * * *",
"next_run_at": "2026-03-03T09:00:00Z",
"last_run_at": "2026-03-02T09:00:00Z",
"last_run_id": "run_789ghi",
"parameters": {...},
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Schedule not found
Example:
curl -X GET https://api.dagy.io/v1/schedules/daily-etl \
-H "Authorization: Bearer {token}"
POST /schedules/{schedule_id}/trigger
Manually trigger a scheduled flow run.
Permissions: runs.trigger
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
schedule_id | string | The unique schedule identifier |
Request Body:
{
"parameters": {
"force_refresh": true,
"custom_param": "value"
}
}
Response (202):
{
"run_id": "run_901jkl",
"run_slug": "my-flow-2026-03-02-120000",
"schedule_id": "daily-etl",
"status": "queued",
"created_at": "2026-03-02T12:00:00Z"
}
Status Codes:
202- Run triggered successfully401- Unauthorized403- Insufficient permissions404- Schedule not found429- Quota exceeded
Example:
curl -X POST https://api.dagy.io/v1/schedules/daily-etl/trigger \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"parameters": {"force_refresh": true}}'
PATCH /schedules/{schedule_id}
Partial update of a schedule. Only supplied fields are changed; all fields are optional.
Permissions: schedules.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
schedule_id | string | The unique schedule identifier |
Request Body:
{
"enabled": false,
"cron_expression": "0 6 * * *",
"timezone": "Europe/London",
"description": "Updated morning run"
}
All fields are optional:
| Field | Type | Description |
|---|---|---|
enabled | boolean | Enable or disable the schedule |
timezone | string | IANA timezone |
catchup_policy | string | skip, all, or none |
cron_expression | string | 5-field cron expression |
interval_seconds | integer | Interval in seconds |
one_time_at | string | ISO 8601 timestamp for one-time schedules |
start_at | string | Schedule start time |
end_at | string | Schedule end time |
parameters | object | Run parameters |
description | string | Schedule description |
environment | string | Target environment |
Response (200):
{
"schedule_id": "daily-etl",
"flow_name": "my-flow",
"flow_version": "5",
"mode": "cron",
"enabled": false,
"timezone": "Europe/London",
"catchup_policy": "skip",
"cron_expression": "0 6 * * *",
"parameters": {},
"next_run_at": "2026-03-03T06:00:00Z",
"description": "Updated morning run",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T11:00:00Z"
}
Status Codes:
200- Schedule updated401- Unauthorized403- Insufficient permissions404- Schedule not found
Example:
curl -X PATCH https://api.dagy.io/v1/schedules/daily-etl \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
API Keys
POST /api-keys
Create a new API key.
Permissions: admin.api_keys
Request Body:
{
"name": "Production ETL Service",
"scopes": [
"flows.read",
"flows.write",
"runs.trigger",
"runs.read"
]
}
Response (201):
{
"key_id": "key_123xyz",
"name": "Production ETL Service",
"key": "dagy_sk_prod_1234567890abcdefghijklmnopqrst",
"key_prefix": "dagy_sk_prod_1234***",
"scopes": [
"flows.read",
"flows.write",
"runs.trigger",
"runs.read"
],
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- API key created (full key shown once only)400- Invalid scopes401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/api-keys \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Production ETL Service",
"scopes": ["flows.read", "runs.trigger"]
}'
GET /api-keys
List all API keys for the authenticated user.
Permissions: admin.api_keys
Response (200):
{
"items": [
{
"key_id": "key_123xyz",
"name": "Production ETL Service",
"key_prefix": "dagy_sk_prod_1234***",
"scopes": [
"flows.read",
"flows.write",
"runs.trigger",
"runs.read"
],
"created_at": "2026-03-02T10:30:00Z",
"last_used_at": "2026-03-02T10:45:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/api-keys \
-H "Authorization: Bearer {token}"
DELETE /api-keys/{key_id}
Delete an API key.
Permissions: admin.api_keys
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
key_id | string | The unique API key identifier |
Response (204): No content
Status Codes:
204- Key deleted successfully401- Unauthorized403- Insufficient permissions404- Key not found
Example:
curl -X DELETE https://api.dagy.io/v1/api-keys/key_123xyz \
-H "Authorization: Bearer {token}"
Organizations & Team
POST /orgs
Create a new organization.
Permissions: None (any authenticated user)
Request Body:
{
"org_name": "Acme Corporation",
"plan": "professional"
}
Response (201):
{
"org_id": "org_456def",
"org_name": "Acme Corporation",
"plan": "professional",
"owner_email": "user@example.com",
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Organization created400- Invalid org name or plan401- Unauthorized409- Organization name already exists
Example:
curl -X POST https://api.dagy.io/v1/orgs \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"org_name": "Acme Corporation",
"plan": "professional"
}'
GET /orgs
List organizations for the authenticated user.
Permissions: None (any authenticated user)
Response (200):
{
"items": [
{
"org_id": "org_456def",
"org_name": "Acme Corporation",
"plan": "professional",
"role": "owner",
"created_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized
Example:
curl -X GET https://api.dagy.io/v1/orgs \
-H "Authorization: Bearer {token}"
POST /orgs/{org_id}/members
Add a member to an organization.
Permissions: admin.members
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Request Body:
{
"email": "newmember@example.com",
"role": "developer"
}
Response (201):
{
"member_id": "member_789ghi",
"email": "newmember@example.com",
"role": "developer",
"added_at": "2026-03-02T10:30:00Z",
"status": "invited"
}
Status Codes:
201- Member invited/added400- Invalid email or role401- Unauthorized403- Insufficient permissions404- Organization not found409- Member already exists
Example:
curl -X POST https://api.dagy.io/v1/orgs/org_456def/members \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"email": "newmember@example.com",
"role": "developer"
}'
GET /orgs/{org_id}/members
List members in an organization.
Permissions: admin.members
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Response (200):
{
"items": [
{
"member_id": "member_123abc",
"email": "owner@example.com",
"role": "owner",
"status": "active",
"joined_at": "2026-02-15T08:00:00Z"
},
{
"member_id": "member_789ghi",
"email": "developer@example.com",
"role": "developer",
"status": "active",
"joined_at": "2026-03-01T14:20:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Organization not found
Example:
curl -X GET https://api.dagy.io/v1/orgs/org_456def/members \
-H "Authorization: Bearer {token}"
GET /orgs/{org_id}/roles
Get the RBAC matrix showing available roles and their permissions.
Permissions: admin.members
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Response (200):
{
"roles": {
"owner": [
"flows.read",
"flows.write",
"flows.delete",
"runs.read",
"runs.trigger",
"runs.cancel",
"schedules.read",
"schedules.write",
"schedules.delete",
"api_keys.read",
"api_keys.write",
"api_keys.delete",
"admin.members",
"admin.api_keys",
"admin.audit",
"secrets.read",
"secrets.write",
"secrets.delete",
"notifications.read",
"notifications.write",
"environments.read",
"environments.write",
"sensors.read",
"sensors.write",
"billing.read",
"billing.write"
],
"admin": [
"flows.read",
"flows.write",
"flows.delete",
"runs.read",
"runs.trigger",
"runs.cancel",
"schedules.read",
"schedules.write",
"api_keys.read",
"api_keys.write",
"admin.members",
"admin.api_keys",
"admin.audit",
"secrets.read",
"secrets.write",
"notifications.read",
"notifications.write",
"environments.read",
"environments.write",
"sensors.read",
"sensors.write",
"billing.read"
],
"developer": [
"flows.read",
"flows.write",
"runs.read",
"runs.trigger",
"runs.cancel",
"schedules.read",
"schedules.write",
"secrets.read",
"secrets.write",
"notifications.read",
"environments.read",
"sensors.read",
"sensors.write"
],
"viewer": [
"flows.read",
"runs.read",
"schedules.read",
"environments.read",
"billing.read"
]
}
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Organization not found
Example:
curl -X GET https://api.dagy.io/v1/orgs/org_456def/roles \
-H "Authorization: Bearer {token}"
PATCH /orgs/{org_id}/members/{email}/role
Update a member's role.
Permissions: admin.members
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
email | string | The member's email address |
Request Body:
{
"role": "admin"
}
Response (200):
{
"member_id": "member_789ghi",
"email": "developer@example.com",
"role": "admin",
"updated_at": "2026-03-02T11:00:00Z"
}
Status Codes:
200- Role updated400- Invalid role401- Unauthorized403- Insufficient permissions404- Organization or member not found
Example:
curl -X PATCH https://api.dagy.io/v1/orgs/org_456def/members/developer@example.com/role \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"role": "admin"}'
DAG Drafts (Visual Builder)
POST /dag-drafts
Create a new DAG draft.
Permissions: flows.write
Request Body:
{
"name": "Data Processing Pipeline v2",
"description": "Process customer data and generate insights",
"canvas_json": {
"nodes": [
{
"id": "node_1",
"type": "s3_read",
"position": [0, 0],
"data": {"bucket": "my-bucket", "key": "customers.csv"}
},
{
"id": "node_2",
"type": "transform",
"position": [200, 0],
"data": {"script": "..."}
}
],
"edges": [
{"id": "edge_1", "source": "node_1", "target": "node_2"}
]
},
"flow_name": "data-processing-v2",
"executor": "kubernetes"
}
Response (201):
{
"draft_id": "draft_345ijk",
"name": "Data Processing Pipeline v2",
"description": "Process customer data and generate insights",
"flow_name": "data-processing-v2",
"executor": "kubernetes",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Draft created400- Invalid canvas JSON401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/dag-drafts \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Data Processing Pipeline v2",
"canvas_json": {...}
}'
GET /dag-drafts
List all DAG drafts.
Permissions: flows.read
Response (200):
{
"items": [
{
"draft_id": "draft_345ijk",
"name": "Data Processing Pipeline v2",
"description": "Process customer data and generate insights",
"flow_name": "data-processing-v2",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/dag-drafts \
-H "Authorization: Bearer {token}"
GET /dag-drafts/{draft_id}
Get a specific DAG draft.
Permissions: flows.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
draft_id | string | The draft identifier |
Response (200):
{
"draft_id": "draft_345ijk",
"name": "Data Processing Pipeline v2",
"description": "Process customer data and generate insights",
"canvas_json": {
"nodes": [...],
"edges": [...]
},
"flow_name": "data-processing-v2",
"executor": "kubernetes",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Draft not found
Example:
curl -X GET https://api.dagy.io/v1/dag-drafts/draft_345ijk \
-H "Authorization: Bearer {token}"
PUT /dag-drafts/{draft_id}
Update a DAG draft.
Permissions: flows.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
draft_id | string | The draft identifier |
Request Body:
{
"name": "Data Processing Pipeline v3",
"description": "Updated description",
"canvas_json": {...}
}
Response (200):
{
"draft_id": "draft_345ijk",
"name": "Data Processing Pipeline v3",
"updated_at": "2026-03-02T11:00:00Z"
}
Status Codes:
200- Draft updated400- Invalid canvas JSON401- Unauthorized403- Insufficient permissions404- Draft not found
Example:
curl -X PUT https://api.dagy.io/v1/dag-drafts/draft_345ijk \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Data Processing Pipeline v3",
"canvas_json": {...}
}'
DELETE /dag-drafts/{draft_id}
Delete a DAG draft.
Permissions: flows.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
draft_id | string | The draft identifier |
Response (204): No content
Status Codes:
204- Draft deleted401- Unauthorized403- Insufficient permissions404- Draft not found
Example:
curl -X DELETE https://api.dagy.io/v1/dag-drafts/draft_345ijk \
-H "Authorization: Bearer {token}"
Usage & Billing
GET /usage/summary
Get current billing period usage summary.
Permissions: billing.read
Response (200):
{
"period_start": "2026-03-01T00:00:00Z",
"period_end": "2026-03-31T23:59:59Z",
"run_count": 450,
"task_count": 2300,
"compute_seconds": 125000,
"api_call_count": 15000,
"error_count": 12,
"estimated_cost_usd": 425.50
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/usage/summary \
-H "Authorization: Bearer {token}"
GET /usage/timeseries
Get daily breakdown of usage.
Permissions: billing.read
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
start_date | string | Start date (ISO 8601) |
end_date | string | End date (ISO 8601) |
Response (200):
{
"items": [
{
"date": "2026-03-01",
"run_count": 15,
"task_count": 78,
"compute_seconds": 4200,
"api_call_count": 500,
"error_count": 1,
"cost_usd": 14.25
},
{
"date": "2026-03-02",
"run_count": 18,
"task_count": 92,
"compute_seconds": 4800,
"api_call_count": 550,
"error_count": 0,
"cost_usd": 16.50
}
]
}
Status Codes:
200- Success400- Invalid date format401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/usage/timeseries?start_date=2026-03-01&end_date=2026-03-15" \
-H "Authorization: Bearer {token}"
GET /usage/quota
Get plan limits and current usage against quota.
Permissions: billing.read
Response (200):
{
"plan": "professional",
"limits": {
"monthly_runs": 10000,
"monthly_tasks": 100000,
"concurrent_runs": 50,
"max_run_duration_seconds": 86400
},
"current_usage": {
"monthly_runs": 450,
"monthly_tasks": 2300,
"concurrent_runs": 3
},
"percentages": {
"monthly_runs": 4.5,
"monthly_tasks": 2.3,
"concurrent_runs": 6
},
"warning": false,
"over_limit": false,
"reset_at": "2026-04-01T00:00:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/usage/quota \
-H "Authorization: Bearer {token}"
GET /usage/dashboard
Comprehensive usage dashboard data.
Permissions: billing.read
Response (200):
{
"summary": {...},
"timeseries": [...],
"quota": {...},
"top_flows": [
{
"flow_name": "my-flow",
"run_count": 250,
"task_count": 1200,
"cost_usd": 180.50
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/usage/dashboard \
-H "Authorization: Bearer {token}"
GET /usage/costs
Cost breakdown by flow and executor.
Permissions: billing.read
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
start_date | string | Start date (ISO 8601) |
end_date | string | End date (ISO 8601) |
Response (200):
{
"total_cost_usd": 425.50,
"by_flow": [
{
"flow_name": "my-flow",
"cost_usd": 250.00,
"run_count": 300
}
],
"by_executor": [
{
"executor": "kubernetes",
"cost_usd": 350.00,
"task_count": 2000
}
]
}
Status Codes:
200- Success400- Invalid date format401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/usage/costs?start_date=2026-03-01&end_date=2026-03-15" \
-H "Authorization: Bearer {token}"
GET /billing/subscription
Get current subscription details.
Permissions: billing.read
Response (200):
{
"subscription_id": "sub_789ghi",
"plan": "professional",
"status": "active",
"billing_cycle_start": "2026-02-02T00:00:00Z",
"billing_cycle_end": "2026-03-02T23:59:59Z",
"billing_email": "billing@example.com",
"monthly_cost_usd": 299.00,
"payment_method": {
"type": "card",
"last_four": "4242",
"expiry_month": 12,
"expiry_year": 2027
}
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/billing/subscription \
-H "Authorization: Bearer {token}"
GET /billing/plans
List available billing plans.
Permissions: None (public endpoint)
Response (200):
{
"items": [
{
"plan_id": "free",
"name": "Free",
"monthly_cost_usd": 0,
"limits": {
"monthly_runs": 100,
"monthly_tasks": 1000,
"concurrent_runs": 2
}
},
{
"plan_id": "professional",
"name": "Professional",
"monthly_cost_usd": 299.00,
"limits": {
"monthly_runs": 10000,
"monthly_tasks": 100000,
"concurrent_runs": 50
}
}
]
}
Status Codes:
200- Success
Example:
curl -X GET https://api.dagy.io/v1/billing/plans
POST /billing/checkout
Create a Stripe checkout session for plan upgrade.
Permissions: billing.write
Request Body:
{
"plan": "professional",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel"
}
Response (201):
{
"checkout_id": "cs_456def",
"url": "https://checkout.stripe.com/pay/cs_456def",
"expires_at": "2026-03-02T11:30:00Z"
}
Status Codes:
201- Checkout session created400- Invalid plan or URLs401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/billing/checkout \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"plan": "professional",
"success_url": "https://example.com/success",
"cancel_url": "https://example.com/cancel"
}'
POST /billing/webhook
Stripe webhook handler for subscription events.
Permissions: None (Stripe signature verification required)
Headers:
Stripe-Signature: {signature}
Request Body: Stripe event object
Response (200):
{
"received": true
}
Status Codes:
200- Event received400- Invalid signature403- Signature verification failed
Example:
curl -X POST https://api.dagy.io/v1/billing/webhook \
-H "Stripe-Signature: t=123456789,v1=..." \
-H "Content-Type: application/json" \
-d '{...stripe event object...}'
POST /billing/portal
Create a Stripe customer portal session.
Permissions: billing.write
Request Body:
{
"return_url": "https://example.com/settings"
}
Response (201):
{
"portal_session_id": "bps_789ghi",
"url": "https://billing.stripe.com/session/bps_789ghi"
}
Status Codes:
201- Portal session created400- Invalid return URL401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/billing/portal \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"return_url": "https://example.com/settings"}'
Audit Logs
GET /audit-logs
List audit logs for the organization.
Permissions: admin.audit
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
resource_type | string | Filter by resource type (flow, run, schedule, api_key, etc.) |
action | string | Filter by action (create, update, delete, execute) |
actor_email | string | Filter by actor email |
start_date | string | Start date (ISO 8601) |
end_date | string | End date (ISO 8601) |
limit | integer | Max results (default: 20, max: 100) |
Response (200):
{
"items": [
{
"audit_id": "audit_123abc",
"timestamp": "2026-03-02T10:30:00Z",
"actor_email": "user@example.com",
"action": "create",
"resource_type": "flow",
"resource_id": "flow_123abc",
"resource_name": "my-flow",
"changes": {
"status": ["pending", "active"]
},
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0..."
}
]
}
Status Codes:
200- Success400- Invalid query parameters401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/audit-logs?resource_type=flow&action=create&limit=50" \
-H "Authorization: Bearer {token}"
GET /audit-logs/resource/{resource_type}/{resource_id}
Get audit trail for a specific resource.
Permissions: admin.audit
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
resource_type | string | Type of resource (flow, run, schedule, api_key) |
resource_id | string | The resource identifier |
Response (200):
{
"resource_id": "flow_123abc",
"resource_type": "flow",
"resource_name": "my-flow",
"items": [
{
"audit_id": "audit_123abc",
"timestamp": "2026-03-02T10:30:00Z",
"actor_email": "user@example.com",
"action": "create",
"changes": {...}
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Resource not found
Example:
curl -X GET https://api.dagy.io/v1/audit-logs/resource/flow/flow_123abc \
-H "Authorization: Bearer {token}"
Secrets Management
POST /secrets
Create a new secret.
Permissions: secrets.write
Request Body:
{
"secret_name": "api-key-prod",
"value": "sk_live_1234567890abcdefghijklmnop",
"environment": "production"
}
Response (201):
{
"secret_id": "secret_123abc",
"secret_name": "api-key-prod",
"environment": "production",
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Secret created400- Invalid secret name or value401- Unauthorized403- Insufficient permissions409- Secret already exists
Example:
curl -X POST https://api.dagy.io/v1/secrets \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"secret_name": "api-key-prod",
"value": "sk_live_1234567890abcdefghijklmnop",
"environment": "production"
}'
GET /secrets
List secrets (metadata only, not values).
Permissions: secrets.read
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
environment | string | Filter by environment |
Response (200):
{
"items": [
{
"secret_id": "secret_123abc",
"secret_name": "api-key-prod",
"environment": "production",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET "https://api.dagy.io/v1/secrets?environment=production" \
-H "Authorization: Bearer {token}"
GET /secrets/{secret_name}
Get secret metadata (not the value).
Permissions: secrets.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
secret_name | string | The secret name |
Response (200):
{
"secret_id": "secret_123abc",
"secret_name": "api-key-prod",
"environment": "production",
"created_at": "2026-03-02T10:30:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Secret not found
Example:
curl -X GET https://api.dagy.io/v1/secrets/api-key-prod \
-H "Authorization: Bearer {token}"
GET /secrets/{secret_name}/value
Get the decrypted secret value. Use with caution.
Permissions: secrets.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
secret_name | string | The secret name |
Response (200):
{
"secret_name": "api-key-prod",
"value": "sk_live_1234567890abcdefghijklmnop"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Secret not found
Example:
curl -X GET https://api.dagy.io/v1/secrets/api-key-prod/value \
-H "Authorization: Bearer {token}"
PUT /secrets/{secret_name}
Update a secret value.
Permissions: secrets.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
secret_name | string | The secret name |
Request Body:
{
"value": "sk_live_new_value_1234567890abcdefg"
}
Response (200):
{
"secret_id": "secret_123abc",
"secret_name": "api-key-prod",
"updated_at": "2026-03-02T11:00:00Z"
}
Status Codes:
200- Secret updated400- Invalid value401- Unauthorized403- Insufficient permissions404- Secret not found
Example:
curl -X PUT https://api.dagy.io/v1/secrets/api-key-prod \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"value": "sk_live_new_value_1234567890abcdefg"}'
DELETE /secrets/{secret_name}
Delete a secret.
Permissions: secrets.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
secret_name | string | The secret name |
Response (204): No content
Status Codes:
204- Secret deleted401- Unauthorized403- Insufficient permissions404- Secret not found
Example:
curl -X DELETE https://api.dagy.io/v1/secrets/api-key-prod \
-H "Authorization: Bearer {token}"
Notifications & Alerts
POST /channels
Create a notification channel.
Permissions: notifications.write
Request Body:
{
"name": "Production Alerts",
"channel_type": "slack",
"config_json": {
"webhook_url": "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX",
"channel": "#alerts"
},
"enabled": true
}
Channel Types:
slack: Slack webhookemail: Email alertswebhook: Custom HTTP webhookpagerduty: PagerDuty integration
Response (201):
{
"channel_id": "channel_123abc",
"name": "Production Alerts",
"channel_type": "slack",
"enabled": true,
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Channel created400- Invalid channel configuration401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/channels \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Alerts",
"channel_type": "slack",
"config_json": {
"webhook_url": "https://hooks.slack.com/services/...",
"channel": "#alerts"
}
}'
GET /channels
List all notification channels.
Permissions: notifications.read
Response (200):
{
"items": [
{
"channel_id": "channel_123abc",
"name": "Production Alerts",
"channel_type": "slack",
"enabled": true,
"created_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/channels \
-H "Authorization: Bearer {token}"
GET /channels/{channel_id}
Get a specific notification channel.
Permissions: notifications.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
channel_id | string | The channel identifier |
Response (200):
{
"channel_id": "channel_123abc",
"name": "Production Alerts",
"channel_type": "slack",
"config_json": {...},
"enabled": true,
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Channel not found
Example:
curl -X GET https://api.dagy.io/v1/channels/channel_123abc \
-H "Authorization: Bearer {token}"
DELETE /channels/{channel_id}
Delete a notification channel.
Permissions: notifications.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
channel_id | string | The channel identifier |
Response (204): No content
Status Codes:
204- Channel deleted401- Unauthorized403- Insufficient permissions404- Channel not found
Example:
curl -X DELETE https://api.dagy.io/v1/channels/channel_123abc \
-H "Authorization: Bearer {token}"
POST /alert-rules
Create an alert rule.
Permissions: notifications.write
Request Body:
{
"name": "Production Flow Failures",
"trigger": "on_failure",
"channel_ids": ["channel_123abc"],
"flow_name": "my-flow",
"sla_seconds": null,
"enabled": true
}
Trigger Types:
on_failure: When run failson_success: When run succeedson_sla_breach: When run exceeds SLAon_retry: When run is retried
Response (201):
{
"rule_id": "rule_123abc",
"name": "Production Flow Failures",
"trigger": "on_failure",
"flow_name": "my-flow",
"channel_ids": ["channel_123abc"],
"enabled": true,
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Rule created400- Invalid rule configuration401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/alert-rules \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "Production Flow Failures",
"trigger": "on_failure",
"channel_ids": ["channel_123abc"],
"flow_name": "my-flow",
"enabled": true
}'
GET /alert-rules
List all alert rules.
Permissions: notifications.read
Response (200):
{
"items": [
{
"rule_id": "rule_123abc",
"name": "Production Flow Failures",
"trigger": "on_failure",
"flow_name": "my-flow",
"channel_ids": ["channel_123abc"],
"enabled": true,
"created_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/alert-rules \
-H "Authorization: Bearer {token}"
GET /alert-rules/{rule_id}
Get a specific alert rule.
Permissions: notifications.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
rule_id | string | The rule identifier |
Response (200):
{
"rule_id": "rule_123abc",
"name": "Production Flow Failures",
"trigger": "on_failure",
"flow_name": "my-flow",
"channel_ids": ["channel_123abc"],
"sla_seconds": null,
"enabled": true,
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Rule not found
Example:
curl -X GET https://api.dagy.io/v1/alert-rules/rule_123abc \
-H "Authorization: Bearer {token}"
DELETE /alert-rules/{rule_id}
Delete an alert rule.
Permissions: notifications.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
rule_id | string | The rule identifier |
Response (204): No content
Status Codes:
204- Rule deleted401- Unauthorized403- Insufficient permissions404- Rule not found
Example:
curl -X DELETE https://api.dagy.io/v1/alert-rules/rule_123abc \
-H "Authorization: Bearer {token}"
POST /alert-rules/{rule_id}/test
Test an alert rule by sending a test notification.
Permissions: notifications.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
rule_id | string | The rule identifier |
Response (200):
{
"status": "sent",
"message": "Test notification sent to 1 channel"
}
Status Codes:
200- Test notification sent401- Unauthorized403- Insufficient permissions404- Rule not found
Example:
curl -X POST https://api.dagy.io/v1/alert-rules/rule_123abc/test \
-H "Authorization: Bearer {token}"
Environments
POST /environments
Create an environment.
Permissions: environments.write
Request Body:
{
"env_name": "staging",
"default_executor": "kubernetes",
"config_json": {
"memory_limit": "4Gi",
"cpu_limit": "2",
"timeout_seconds": 3600
},
"is_protected": false
}
Response (201):
{
"env_id": "env_123abc",
"env_name": "staging",
"default_executor": "kubernetes",
"is_protected": false,
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Environment created400- Invalid configuration401- Unauthorized403- Insufficient permissions409- Environment already exists
Example:
curl -X POST https://api.dagy.io/v1/environments \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"env_name": "staging",
"default_executor": "kubernetes"
}'
GET /environments
List all environments.
Permissions: environments.read
Response (200):
{
"items": [
{
"env_id": "env_123abc",
"env_name": "staging",
"default_executor": "kubernetes",
"is_protected": false,
"created_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/environments \
-H "Authorization: Bearer {token}"
GET /environments/{env_name}
Get a specific environment.
Permissions: environments.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | The environment name |
Response (200):
{
"env_id": "env_123abc",
"env_name": "staging",
"default_executor": "kubernetes",
"config_json": {...},
"is_protected": false,
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Environment not found
Example:
curl -X GET https://api.dagy.io/v1/environments/staging \
-H "Authorization: Bearer {token}"
PATCH /environments/{env_name}
Update an environment.
Permissions: environments.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | The environment name |
Request Body:
{
"default_executor": "lambda",
"config_json": {...},
"is_protected": true
}
Response (200):
{
"env_id": "env_123abc",
"env_name": "staging",
"default_executor": "lambda",
"is_protected": true,
"updated_at": "2026-03-02T11:00:00Z"
}
Status Codes:
200- Environment updated400- Invalid configuration401- Unauthorized403- Insufficient permissions404- Environment not found
Example:
curl -X PATCH https://api.dagy.io/v1/environments/staging \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"default_executor": "lambda"}'
DELETE /environments/{env_name}
Delete an environment.
Permissions: environments.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | The environment name |
Response (204): No content
Status Codes:
204- Environment deleted401- Unauthorized403- Insufficient permissions404- Environment not found
Error Response (404, protected environment):
{
"error": "protected_environment",
"message": "Cannot delete protected environment 'production'"
}
Example:
curl -X DELETE https://api.dagy.io/v1/environments/staging \
-H "Authorization: Bearer {token}"
GET /environments/{env_name}/variables
Get all variables for an environment.
Permissions: environments.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | The environment name |
Response (200):
{
"env_name": "staging",
"variables": {
"DATABASE_URL": "postgres://staging-db:5432/app",
"API_KEY": "sk_staging_xxx",
"LOG_LEVEL": "debug"
}
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Environment not found
Example:
curl -X GET https://api.dagy.io/v1/environments/staging/variables \
-H "Authorization: Bearer {token}"
PUT /environments/{env_name}/variables
Replace all variables for an environment. Existing variables are overwritten.
Permissions: environments.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | The environment name |
Request Body:
{
"variables": {
"DATABASE_URL": "postgres://staging-db:5432/app",
"API_KEY": "sk_staging_new",
"LOG_LEVEL": "info"
}
}
Response (200):
{
"env_name": "staging",
"variables": {
"DATABASE_URL": "postgres://staging-db:5432/app",
"API_KEY": "sk_staging_new",
"LOG_LEVEL": "info"
}
}
Status Codes:
200- Variables replaced400-variablesmust be a JSON object401- Unauthorized403- Insufficient permissions404- Environment not found
Example:
curl -X PUT https://api.dagy.io/v1/environments/staging/variables \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"variables": {"LOG_LEVEL": "info", "API_KEY": "sk_staging_new"}}'
PATCH /environments/{env_name}/variables
Merge variables into an environment. Adds or updates supplied keys without removing existing ones.
Permissions: environments.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | The environment name |
Request Body:
{
"variables": {
"NEW_VAR": "value",
"LOG_LEVEL": "debug"
}
}
Response (200):
{
"env_name": "staging",
"variables": {
"DATABASE_URL": "postgres://staging-db:5432/app",
"API_KEY": "sk_staging_xxx",
"LOG_LEVEL": "debug",
"NEW_VAR": "value"
}
}
Status Codes:
200- Variables merged400-variablesmust be a JSON object401- Unauthorized403- Insufficient permissions404- Environment not found
Example:
curl -X PATCH https://api.dagy.io/v1/environments/staging/variables \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"variables": {"NEW_VAR": "value"}}'
DELETE /environments/{env_name}/variables/{var_key}
Delete a single variable from an environment.
Permissions: environments.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | The environment name |
var_key | string | The variable key to delete |
Response (200):
{
"deleted": "LOG_LEVEL",
"env_name": "staging"
}
Status Codes:
200- Variable deleted401- Unauthorized403- Insufficient permissions404- Environment or variable not found
Example:
curl -X DELETE https://api.dagy.io/v1/environments/staging/variables/LOG_LEVEL \
-H "Authorization: Bearer {token}"
PUT /environments/reorder
Set the promotion order for all environments based on the submitted ordering.
Permissions: environments.write
Request Body:
{
"order": ["develop", "staging", "production"]
}
Response (200):
{
"items": [
{
"env_name": "develop",
"promotion_order": 0,
...
},
{
"env_name": "staging",
"promotion_order": 1,
...
},
{
"env_name": "production",
"promotion_order": 2,
...
}
]
}
Status Codes:
200- Order updated401- Unauthorized403- Insufficient permissions
Example:
curl -X PUT https://api.dagy.io/v1/environments/reorder \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"order": ["develop", "staging", "production"]}'
GET /environments/{env_name}/diff/{target_env}
Compare deployments between two environments. Returns deployments that exist in only one environment and deployments with version differences.
Permissions: environments.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
env_name | string | Source environment name |
target_env | string | Target environment name |
Response (200):
{
"source_environment": "staging",
"target_environment": "production",
"only_in_source": [
{
"deployment_name": "new-flow-staging",
"flow_name": "new-flow",
"flow_version": "1",
"updated_at": "2026-03-02T10:00:00Z"
}
],
"only_in_target": [],
"version_differs": [
{
"flow_name": "my-flow",
"source": {
"deployment_name": "my-flow-staging",
"flow_name": "my-flow",
"flow_version": "4",
"updated_at": "2026-03-02T10:30:00Z"
},
"target": {
"deployment_name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "3",
"updated_at": "2026-03-01T14:00:00Z"
}
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Source or target environment not found
Example:
curl -X GET https://api.dagy.io/v1/environments/staging/diff/production \
-H "Authorization: Bearer {token}"
POST /environments/promote
Promote a deployment from one environment to another. Creates a new deployment record in the target environment using the same flow and version from the source.
Permissions: environments.write
Request Body:
{
"source_deployment": "my-flow-staging",
"target_environment": "production",
"new_deployment_name": "my-flow-prod",
"flow_version": "3"
}
| Field | Type | Required | Description |
|---|---|---|---|
source_deployment | string | Yes | Deployment name to promote from |
target_environment | string | Yes | Target environment name |
new_deployment_name | string | No | Override deployment name in target |
flow_version | string | No | Override flow version (defaults to source version) |
Response (201):
{
"target_deployment": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "3",
"target_environment": "production"
}
Status Codes:
201- Deployment promoted400- Missing required fields or validation error401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/environments/promote \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"source_deployment": "my-flow-staging",
"target_environment": "production"
}'
Sensors
POST /sensors
Create a sensor to trigger flows based on events.
Permissions: sensors.write
Request Body:
{
"name": "S3 Data Sync",
"sensor_type": "s3",
"flow_name": "data-pipeline",
"config_json": {
"bucket": "my-bucket",
"prefix": "incoming/",
"events": ["s3:ObjectCreated:*"]
},
"flow_params_json": {
"source_bucket": "my-bucket",
"environment": "prod"
},
"enabled": true
}
Sensor Types:
s3: S3 bucket eventswebhook: HTTP webhook (auto-generates token)polling: Polling-based trigger
Response (201):
{
"sensor_id": "sensor_123abc",
"name": "S3 Data Sync",
"sensor_type": "s3",
"flow_name": "data-pipeline",
"enabled": true,
"webhook_token": "wh_123456789abcdefghijklmnop",
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
201- Sensor created400- Invalid configuration401- Unauthorized403- Insufficient permissions404- Flow not found
Example:
curl -X POST https://api.dagy.io/v1/sensors \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"name": "S3 Data Sync",
"sensor_type": "s3",
"flow_name": "data-pipeline",
"config_json": {
"bucket": "my-bucket",
"prefix": "incoming/"
}
}'
GET /sensors
List all sensors.
Permissions: sensors.read
Response (200):
{
"items": [
{
"sensor_id": "sensor_123abc",
"name": "S3 Data Sync",
"sensor_type": "s3",
"flow_name": "data-pipeline",
"enabled": true,
"created_at": "2026-03-02T10:30:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/sensors \
-H "Authorization: Bearer {token}"
GET /sensors/{sensor_id}
Get a specific sensor.
Permissions: sensors.read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
sensor_id | string | The sensor identifier |
Response (200):
{
"sensor_id": "sensor_123abc",
"name": "S3 Data Sync",
"sensor_type": "s3",
"flow_name": "data-pipeline",
"config_json": {...},
"flow_params_json": {...},
"enabled": true,
"webhook_token": "wh_123456789abcdefghijklmnop",
"created_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions404- Sensor not found
Example:
curl -X GET https://api.dagy.io/v1/sensors/sensor_123abc \
-H "Authorization: Bearer {token}"
DELETE /sensors/{sensor_id}
Delete a sensor.
Permissions: sensors.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
sensor_id | string | The sensor identifier |
Response (204): No content
Status Codes:
204- Sensor deleted401- Unauthorized403- Insufficient permissions404- Sensor not found
Example:
curl -X DELETE https://api.dagy.io/v1/sensors/sensor_123abc \
-H "Authorization: Bearer {token}"
POST /sensors/{sensor_id}/test
Test a sensor to verify it works correctly.
Permissions: sensors.write
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
sensor_id | string | The sensor identifier |
Response (200):
{
"status": "success",
"message": "Test event triggered successfully",
"run_id": "run_901jkl"
}
Status Codes:
200- Test successful400- Test failed (check sensor config)401- Unauthorized403- Insufficient permissions404- Sensor not found
Example:
curl -X POST https://api.dagy.io/v1/sensors/sensor_123abc/test \
-H "Authorization: Bearer {token}"
POST /webhooks/{token}
Public webhook endpoint for event ingestion.
Permissions: None (token-based authentication)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
token | string | The webhook token from sensor |
Request Body: Any JSON payload
{
"event": "custom_event",
"data": {...}
}
Response (202):
{
"run_id": "run_123abc",
"status": "queued",
"message": "Event received and flow triggered"
}
Status Codes:
202- Event accepted400- Invalid payload404- Webhook token not found or sensor disabled429- Rate limit exceeded
Example:
curl -X POST https://api.dagy.io/v1/webhooks/wh_123456789abcdefghijklmnop \
-H "Content-Type: application/json" \
-d '{"event": "data_ready", "data": {"file": "data.csv"}}'
Artifacts
Multipart artifact upload endpoints used by the dagy deploy CLI workflow. The upload follows a three-phase flow: initiate (get presigned URLs), upload parts to S3, then complete or abort.
POST /artifacts/initiate
Initiate a multipart artifact upload. Returns presigned S3 URLs for each upload part.
Permissions: flows.write
Request Body:
{
"flow_name": "my-flow",
"flow_version": "3",
"deployment_name": "my-flow-prod",
"file_size": 25000000,
"part_size": 8388608
}
| Field | Type | Required | Description |
|---|---|---|---|
flow_name | string | Yes | The flow name |
flow_version | string | Yes | The flow version |
deployment_name | string | Yes | Deployment name |
file_size | integer | Yes | Total file size in bytes |
part_size | integer | No | Part size in bytes (default: 8 MB, min: 5 MB) |
Response (200):
{
"upload_id": "abc123...",
"artifact_key": "dagy/flows/org_456def/my-flow/3/artifact.zip",
"parts": [
{
"part_number": 1,
"url": "https://s3.amazonaws.com/...",
"size": 8388608
},
{
"part_number": 2,
"url": "https://s3.amazonaws.com/...",
"size": 8388608
},
{
"part_number": 3,
"url": "https://s3.amazonaws.com/...",
"size": 8222784
}
],
"expires_at": "2026-03-02T11:30:00Z"
}
Status Codes:
200- Upload initiated401- Unauthorized403- Insufficient permissions500- Artifact storage not configured
Example:
curl -X POST https://api.dagy.io/v1/artifacts/initiate \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"flow_name": "my-flow",
"flow_version": "3",
"deployment_name": "my-flow-prod",
"file_size": 25000000
}'
POST /artifacts/complete
Complete a multipart upload after all parts have been uploaded to S3. Registers the flow version and creates a deployment.
Permissions: flows.write
Request Body:
{
"upload_id": "abc123...",
"artifact_key": "dagy/flows/org_456def/my-flow/3/artifact.zip",
"parts": [
{"part_number": 1, "etag": "\"a1b2c3...\""},
{"part_number": 2, "etag": "\"d4e5f6...\""},
{"part_number": 3, "etag": "\"g7h8i9...\""}
],
"deployment_name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "3",
"status": "ACTIVE",
"schedule": "0 9 * * *",
"timezone": "America/New_York",
"default_executor": "lambda",
"tags": {"team": "data-eng"},
"namespace": "data/ingestion",
"environment": "production",
"execution_mode": "micro",
"flow_spec": {...},
"code_hash": "a1b2c3d4e5f6..."
}
Response (200):
{
"artifact_s3_uri": "s3://dagy-artifacts/dagy/flows/org_456def/my-flow/3/artifact.zip",
"flow_name": "my-flow",
"flow_version": "3",
"deployment_name": "my-flow-prod",
"deployment_version": 3,
"environment": "production",
"code_hash": "a1b2c3d4e5f6..."
}
Status Codes:
200- Upload completed, flow registered400- Failed to complete upload401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/artifacts/complete \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "abc123...",
"artifact_key": "dagy/flows/org_456def/my-flow/3/artifact.zip",
"parts": [{"part_number": 1, "etag": "\"a1b2c3...\""}],
"deployment_name": "my-flow-prod",
"flow_name": "my-flow",
"flow_version": "3"
}'
POST /artifacts/abort
Abort a multipart upload in progress. Cleans up partial uploads from S3.
Permissions: flows.write
Request Body:
{
"upload_id": "abc123...",
"artifact_key": "dagy/flows/org_456def/my-flow/3/artifact.zip"
}
Response (204): No content
Status Codes:
204- Upload aborted401- Unauthorized403- Insufficient permissions
Example:
curl -X POST https://api.dagy.io/v1/artifacts/abort \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "abc123...",
"artifact_key": "dagy/flows/org_456def/my-flow/3/artifact.zip"
}'
UI Config
Endpoints that return configuration and content for the web frontend. Marketing and navigation are public; dashboard and settings require authentication.
GET /ui/marketing
Get marketing content for the landing page.
Permissions: None (public)
Response (200):
{
"hero": {...},
"features": [...],
"pricing": {...},
"testimonials": [...]
}
Status Codes:
200- Success
Example:
curl -X GET https://api.dagy.io/v1/ui/marketing
GET /ui/navigation
Get navigation structure for the web app.
Permissions: None (public)
Response (200):
{
"main_nav": [...],
"sidebar_nav": [...],
"footer_nav": [...]
}
Status Codes:
200- Success
Example:
curl -X GET https://api.dagy.io/v1/ui/navigation
GET /ui/dashboard
Get dashboard configuration and widget content.
Permissions: runs.read
Response (200):
{
"widgets": [...],
"stats": {...},
"recent_activity": [...]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/ui/dashboard \
-H "Authorization: Bearer {token}"
GET /ui/settings
Get settings page configuration.
Permissions: Authenticated user
Response (200):
{
"sections": [...],
"integrations": [...],
"preferences": {...}
}
Status Codes:
200- Success401- Unauthorized
Example:
curl -X GET https://api.dagy.io/v1/ui/settings \
-H "Authorization: Bearer {token}"
AI Studio
Session-based agentic workflow building. AI Studio now runs exclusively through /ai-studio/sessions/*; the legacy one-shot generation endpoints are retired and hidden from the launch contract.
GET /ui/ai-studio
Get AI Studio page configuration and content.
Permissions: flows.read
Response (200):
{
"templates": [...],
"examples": [...],
"capabilities": [...]
}
Status Codes:
200- Success401- Unauthorized403- Insufficient permissions
Example:
curl -X GET https://api.dagy.io/v1/ui/ai-studio \
-H "Authorization: Bearer {token}"
Session Endpoints
| Endpoint | Description |
|---|---|
GET /ai-studio/tiers | List available model tiers, limits, and credit balance |
POST /ai-studio/sessions | Create a new AI Studio session |
GET /ai-studio/sessions | List current user's sessions |
GET /ai-studio/sessions/by-flow/{flow_name} | Resolve the session bound to a flow |
POST /ai-studio/sessions/{session_id}/bind-flow | Bind a session to a flow |
GET /ai-studio/sessions/{session_id} | Fetch a full session with history, canvas, attachments, and artifacts |
DELETE /ai-studio/sessions/{session_id} | Delete a session |
POST /ai-studio/sessions/{session_id}/archive | Archive a session |
POST /ai-studio/sessions/{session_id}/messages | Send a synchronous message turn |
POST /ai-studio/sessions/{session_id}/messages/stream | Stream a message turn as SSE |
PATCH /ai-studio/sessions/{session_id}/canvas | Persist canvas edits |
PATCH /ai-studio/sessions/{session_id}/tier | Change the model tier for a session |
PATCH /ai-studio/sessions/{session_id}/title | Rename a session |
POST /ai-studio/sessions/{session_id}/attachments/initiate | Initiate a single-part attachment upload |
POST /ai-studio/sessions/{session_id}/attachments/complete | Complete upload, parse the attachment, and attach it to the session |
DELETE /ai-studio/sessions/{session_id}/attachments/{attachment_id} | Remove an attachment from the session |
Permissions: flows.write
Request Body:
{
"message": "Build a workflow that reads JSON from S3, validates schema, normalizes records, deduplicates, and writes back to S3.",
"intent": "build",
"turn_id": "turn_123"
}
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Natural language request (max 8000 chars) |
intent | string | No | One of auto, build, modify, or advisory |
turn_id | string | No | Stable client turn identifier for retry safety |
Synchronous Response (200 / 422):
{
"session": {...},
"response": "I created the workflow and validated it.",
"canvas_state": {
"nodes": [...],
"edges": [...]
},
"pipeline_yaml": "name: generated_pipeline\n...",
"pipeline_python": "from dagy import flow, task\n...",
"credits_used": 4.0,
"workflow_status": "generated",
"workflow_errors": [],
"validation": {
"valid": true,
"errors": [],
"warnings": []
},
"research_sources": [
{"title": "AWS S3 docs", "url": "https://docs.aws.amazon.com/..."}
]
}
Status Codes:
200- Successful advisory/build/modify turn422-buildormodifyrequest did not produce a valid workflow400- Invalid request body402- Insufficient credits404- Session not found409- Duplicate turn or idempotent replay429- Session turn or attachment limit reached401- Unauthorized403- Insufficient permissions
Attachment Constraints:
- Single-part presigned uploads only
- Maximum size: 25 MB
- Supported types: PDF, DOCX, PPTX, CSV, TXT, MD, JSON, YAML
- Image/OCR uploads are rejected for launch
Executable Node Allowlist:
s3_ingests3_writehttp_fetchschema_validatordata_normalizerdeduplicatorpython_functionwebhook_post
Example:
curl -X POST https://api.dagy.io/v1/ai-studio/sessions/ss_123/messages \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: req-123" \
-d '{"message": "Build an S3 validation workflow", "intent": "build", "turn_id": "turn-123"}'
Super Admin
Platform-level administrative endpoints. All endpoints in this section require the super_admin flag on the user identity. This is separate from org-scoped RBAC roles; super admin is a platform-level boolean that gates cross-org operations.
All super admin endpoints return 403 Super admin access required if the identity does not have super_admin: true.
GET /admin/customers
List all customer organizations with filtering support.
Permissions: Super admin
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
status | string | Filter by status (active, suspended) |
plan | string | Filter by plan (free, pro, enterprise) |
search | string | Search in org name and owner email |
Response (200):
{
"items": [
{
"org_id": "org_456def",
"org_name": "Acme Corporation",
"owner_email": "admin@acme.com",
"plan": "professional",
"status": "active",
"member_count": 12,
"created_at": "2026-01-15T08:00:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
],
"total": 1
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET "https://api.dagy.io/v1/admin/customers?status=active&plan=professional" \
-H "Authorization: Bearer {token}"
GET /admin/customers/{org_id}
Get detailed information about a customer organization.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Response (200):
{
"org_id": "org_456def",
"org_name": "Acme Corporation",
"owner_email": "admin@acme.com",
"plan": "professional",
"status": "active",
"quota_overrides": {},
"feature_flags": {},
"notes": "VIP customer",
"credits_balance": 500.0,
"created_at": "2026-01-15T08:00:00Z",
"updated_at": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required404- Organization not found
Example:
curl -X GET https://api.dagy.io/v1/admin/customers/org_456def \
-H "Authorization: Bearer {token}"
PATCH /admin/customers/{org_id}
Update customer organization details.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Request Body:
{
"status": "active",
"plan": "enterprise",
"quota_overrides": {"monthly_runs": 50000},
"feature_flags": {"beta_ai_studio": true},
"notes": "Upgraded to enterprise",
"credits_balance": 1000.0
}
All fields are optional.
Response (200): Updated customer detail object.
Status Codes:
200- Customer updated400- No fields to update401- Unauthorized403- Super admin access required404- Organization not found
Example:
curl -X PATCH https://api.dagy.io/v1/admin/customers/org_456def \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"plan": "enterprise", "notes": "Upgraded"}'
GET /admin/customers/{org_id}/members
List members of a customer organization.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Response (200):
{
"items": [
{
"user_email": "admin@acme.com",
"role": "owner",
"joined_at": "2026-01-15T08:00:00Z"
},
{
"user_email": "dev@acme.com",
"role": "developer",
"joined_at": "2026-02-01T10:00:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET https://api.dagy.io/v1/admin/customers/org_456def/members \
-H "Authorization: Bearer {token}"
GET /admin/customers/{org_id}/usage
Get usage data for a customer organization including monthly aggregate and daily timeseries.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Response (200):
{
"items": [
{
"org_id": "org_456def",
"period": "2026-03",
"run_count": 450,
"task_count": 2300,
"compute_seconds": 125000,
"api_call_count": 15000,
"error_count": 12,
"estimated_cost_usd": 425.50
}
]
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET https://api.dagy.io/v1/admin/customers/org_456def/usage \
-H "Authorization: Bearer {token}"
GET /admin/customers/{org_id}/audit
Get audit logs for a customer organization.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Max results (default: 50) |
Response (200):
{
"items": [
{
"org_id": "org_456def",
"event_time": "2026-03-02T10:30:00Z",
"resource_type": "flow",
"resource_id": "my-flow",
"action": "create",
"actor_email": "dev@acme.com",
"metadata": {}
}
]
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET "https://api.dagy.io/v1/admin/customers/org_456def/audit?limit=20" \
-H "Authorization: Bearer {token}"
GET /admin/customers/{org_id}/sessions
List active sessions for a customer organization.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Response (200):
{
"items": [
{
"user_email": "admin@acme.com",
"last_login_at": "2026-03-02T09:00:00Z",
"current_token_issued_at": "2026-03-02T09:00:00Z"
}
]
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET https://api.dagy.io/v1/admin/customers/org_456def/sessions \
-H "Authorization: Bearer {token}"
POST /admin/customers/{org_id}/suspend
Suspend a customer organization. Blocks all non-admin access.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Request Body: None
Response (200):
{
"status": "suspended",
"org_id": "org_456def"
}
Status Codes:
200- Organization suspended401- Unauthorized403- Super admin access required404- Organization not found
Example:
curl -X POST https://api.dagy.io/v1/admin/customers/org_456def/suspend \
-H "Authorization: Bearer {token}"
POST /admin/customers/{org_id}/activate
Reactivate a suspended customer organization.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Request Body: None
Response (200):
{
"status": "active",
"org_id": "org_456def"
}
Status Codes:
200- Organization activated401- Unauthorized403- Super admin access required404- Organization not found
Example:
curl -X POST https://api.dagy.io/v1/admin/customers/org_456def/activate \
-H "Authorization: Bearer {token}"
POST /admin/customers/{org_id}/credits
Adjust credits balance for a customer organization.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
Request Body:
{
"amount": 500.0
}
The amount can be positive (add credits) or negative (deduct credits).
Response (200):
{
"org_id": "org_456def",
"credits_balance": 1500.0
}
Status Codes:
200- Credits adjusted400- Invalid amount401- Unauthorized403- Super admin access required404- Organization not found
Example:
curl -X POST https://api.dagy.io/v1/admin/customers/org_456def/credits \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"amount": 500.0}'
DELETE /admin/customers/{org_id}/sessions/{user_email}
Revoke all active sessions for a user in a customer organization.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization identifier |
user_email | string | The user's email address |
Response (200):
{
"status": "revoked",
"user_email": "dev@acme.com",
"deleted": 2
}
Status Codes:
200- Sessions revoked401- Unauthorized403- Super admin access required
Example:
curl -X DELETE https://api.dagy.io/v1/admin/customers/org_456def/sessions/dev@acme.com \
-H "Authorization: Bearer {token}"
GET /admin/exceptions
List exception traces across all organizations.
Permissions: Super admin
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Max results (default: 100) |
Response (200):
{
"items": [...]
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET "https://api.dagy.io/v1/admin/exceptions?limit=50" \
-H "Authorization: Bearer {token}"
GET /admin/exceptions/content
Get the content of a specific exception trace.
Permissions: Super admin
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
key | string | The exception trace key |
Response (200): Trace content object.
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET "https://api.dagy.io/v1/admin/exceptions/content?key=trace_123" \
-H "Authorization: Bearer {token}"
GET /admin/exceptions/download
Get a presigned download URL for an exception trace.
Permissions: Super admin
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
key | string | The exception trace key |
Response (200):
{
"download_url": "https://s3.amazonaws.com/..."
}
Status Codes:
200- Success401- Unauthorized403- Super admin access required
Example:
curl -X GET "https://api.dagy.io/v1/admin/exceptions/download?key=trace_123" \
-H "Authorization: Bearer {token}"
DELETE /admin/exceptions
Delete exception traces. Requires s3:DeleteObject IAM permission.
Permissions: Super admin
Request Body:
{
"keys": ["trace_key_1", "trace_key_2"]
}
Response (200):
{
"deleted": 2
}
Status Codes:
200- Traces deleted401- Unauthorized403- Super admin access required
Example:
curl -X DELETE https://api.dagy.io/v1/admin/exceptions \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"keys": ["trace_key_1", "trace_key_2"]}'
POST /admin/impersonate/{org_id}
Impersonate a customer organization. The response can be used to set X-Impersonate-Org header on subsequent requests, switching the super admin's context to the specified organization with owner-level access.
Permissions: Super admin
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
org_id | string | The organization to impersonate |
Request Body: None
Response (200):
{
"org_id": "org_456def",
"org_name": "Acme Corporation",
"impersonating": true
}
Status Codes:
200- Impersonation started401- Unauthorized403- Super admin access required404- Organization not found
Example:
curl -X POST https://api.dagy.io/v1/admin/impersonate/org_456def \
-H "Authorization: Bearer {token}"
Node Registry
The Node Registry API manages flow node definitions — both built-in nodes and custom nodes created by your organization. These endpoints power the Flow Designer's node panel.
GET /nodes/registry
List all available node types (built-in + custom for the org).
Query Parameters:
org_id(optional) — Filter to include custom nodes for this org
Response:
{
"items": [
{
"node_type": "s3_ingest",
"label": "S3 Ingest",
"description": "Read files from Amazon S3",
"category": "ingestion",
"icon": "CloudDownload",
"color": "blue",
"official": true,
"connectors": [
{"id": "trigger_in", "name": "Trigger", "direction": "inbound", "data_types": ["trigger", "any"], "cardinality": "zero_or_one", "required": false},
{"id": "data_out", "name": "Data", "direction": "outbound", "data_types": ["dataframe", "json", "string"], "cardinality": "zero_or_many", "required": false}
],
"config_schema": [...]
}
],
"total": 27,
"builtin_count": 27,
"custom_count": 0
}
GET /nodes/registry/builtin
List only built-in nodes (no org-specific custom nodes).
GET /nodes/registry/custom
List custom nodes registered by the org.
Headers: X-Org-Id (required)
GET /nodes/registry/categories
List available categories with node counts.
Response:
{
"categories": [
{"id": "ingestion", "label": "Ingestion", "count": 5},
{"id": "transform", "label": "Transform", "count": 5},
{"id": "llm", "label": "LLM", "count": 4},
{"id": "vectordb", "label": "Vector DB", "count": 3},
{"id": "notification", "label": "Notifications", "count": 4},
{"id": "custom", "label": "Custom Code", "count": 3},
{"id": "control_flow", "label": "Control Flow", "count": 3}
]
}
GET /nodes/registry/search
Search nodes by query string.
Query Parameters:
q(required) — Search query (matches node_type, label, description, tags)org_id(optional) — Include org's custom nodes in results
GET /nodes/registry/{node_type}
Get a single node definition by its type identifier.
Path Parameters:
node_type— The node type ID (e.g., "s3_ingest", "schema_validator")
POST /nodes/registry
Register a new custom node for your org.
Headers: X-Org-Id (required)
Request Body:
{
"node_type": "custom_enricher",
"label": "Data Enricher",
"description": "Enriches records with external data",
"category": "transform",
"icon": "Sparkles",
"color": "violet",
"connectors": [
{"id": "data_in", "name": "Input", "direction": "inbound", "data_types": ["json", "dataframe"], "cardinality": "zero_or_many", "required": true},
{"id": "enriched_out", "name": "Enriched", "direction": "outbound", "data_types": ["json"], "cardinality": "zero_or_many"}
],
"config_schema": [
{"name": "api_url", "label": "API URL", "type": "string", "required": true},
{"name": "api_key_ref", "label": "API Key", "type": "string", "required": true}
],
"import_path": "my_org.nodes.enricher:DataEnricherNode"
}
Response: 201 Created with the full node definition.
PUT /nodes/registry/{node_type}
Update an existing custom node. A version snapshot is saved before the update.
Headers: X-Org-Id (required)
Request Body: Same as POST (partial updates supported).
DELETE /nodes/registry/{node_type}
Delete a custom node. Only custom (non-official) nodes can be deleted.
Headers: X-Org-Id (required)
POST /nodes/validate-connection
Validate whether a connection between two node connectors is allowed.
Request Body:
{
"source_node_type": "s3_ingest",
"source_connector_id": "data_out",
"target_node_type": "schema_validator",
"target_connector_id": "data_in",
"existing_source_connections": 0,
"existing_target_connections": 0
}
Response:
{
"valid": true,
"errors": [],
"source_connector": {"id": "data_out", "name": "Data", "data_types": ["dataframe", "json", "string"]},
"target_connector": {"id": "data_in", "name": "Data", "data_types": ["json", "dataframe", "any"]}
}
Health & Monitoring
GET /health
Basic health check endpoint.
Permissions: None (public)
Response (200):
{
"status": "healthy",
"version": "1.2.0",
"components": [
{
"name": "api",
"status": "healthy"
},
{
"name": "database",
"status": "healthy"
},
{
"name": "cache",
"status": "healthy"
}
],
"timestamp": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Healthy503- Degraded or unhealthy
Example:
curl -X GET https://api.dagy.io/v1/health
GET /health/detailed
Detailed health check with latency information.
Permissions: Authenticated user
Response (200):
{
"status": "healthy",
"version": "1.2.0",
"components": [
{
"name": "api",
"status": "healthy",
"latency_ms": 2
},
{
"name": "database",
"status": "healthy",
"latency_ms": 25
},
{
"name": "cache",
"status": "healthy",
"latency_ms": 5
},
{
"name": "s3",
"status": "healthy",
"latency_ms": 150
}
],
"timestamp": "2026-03-02T10:30:00Z"
}
Status Codes:
200- Success401- Unauthorized503- Service degraded
Example:
curl -X GET https://api.dagy.io/v1/health/detailed \
-H "Authorization: Bearer {token}"
Common Patterns
Authentication
All endpoints (except /auth/login, /health, /billing/webhook, /billing/plans, and /webhooks/{token}) require authentication via Bearer token or API key.
Bearer Token:
Authorization: Bearer {access_token}
API Key:
Authorization: Bearer dagy_sk_prod_1234567890abcdefghijklmnop
Error Responses
Standard error response format:
{
"error": "error_code",
"message": "Human-readable error message",
"details": {
"field": "validation error details"
}
}
Rate Limiting
All responses include rate limiting headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1646231400
On rate limit (429):
Retry-After: 60
Pagination
Paginated endpoints support limit and next_token parameters:
GET /flows?limit=20&next_token=eyJvZmZzZXQiOiAyMH0=
Response includes:
{
"items": [...],
"next_token": "eyJvZmZzZXQiOiA0MH0=" // null if last page
}
Status Codes
| Status | Meaning |
|---|---|
200 | Success |
201 | Created |
202 | Accepted (async operation) |
204 | No Content |
400 | Bad Request / Validation Error |
401 | Unauthorized |
403 | Forbidden (insufficient permissions) |
404 | Not Found |
409 | Conflict (e.g., resource already exists) |
429 | Rate Limited or Quota Exceeded |
500 | Internal Server Error |
503 | Service Unavailable |
Timestamps
All timestamps are in ISO 8601 format:
2026-03-02T10:30:00Z
Permissions (RBAC)
Permissions follow the pattern resource.action:
flows.read- Read flow definitionsflows.write- Create/update flowsflows.delete- Delete flowsruns.read- View run historyruns.trigger- Trigger new runsruns.cancel- Cancel running runsadmin.api_keys- Manage API keysadmin.members- Manage organization membersadmin.audit- View audit logsbilling.read- View billing and usagebilling.write- Modify billing settings
Available roles:
- Owner: Full access to all resources
- Admin: All permissions except billing write
- Developer: Create/manage flows, runs, schedules; read only on billing
- Viewer: Read-only access to flows, runs, and billing
API Version: v1 Last Updated: 2026-03-09