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
- Connectors
- 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"
}'
Connectors
Workspace connector settings are persisted per organization. Credential fields supplied in
config are encrypted before storage and are never returned by read APIs. Responses expose only
safe public configuration plus configured_secret_fields, which lets clients show that a secret
exists without receiving its value.
| Method | Path | Permission | Purpose |
|---|---|---|---|
GET | /connectors/ | connectors.read | List the workspace's connectors |
GET | /connectors/{connector_id} | connectors.read | Read one connector |
POST | /connectors/ | connectors.write | Create and persist a connector |
PUT | /connectors/{connector_id} | connectors.write | Update configuration or enabled state |
DELETE | /connectors/{connector_id} | connectors.delete | Delete a connector and its credentials |
POST | /connectors/{connector_id}/test | connectors.write | Run and persist a live connection check |
Create a connector
{
"name": "Primary OpenAI",
"connector_type_id": "openai",
"enabled": true,
"config": {
"apiKey": "sk-secret-value",
"defaultModel": "gpt-4o-mini"
}
}
The 201 response omits the secret value:
{
"connector_id": "6f7e...",
"org_id": "org_456def",
"name": "Primary OpenAI",
"connector_type_id": "openai",
"config": {"defaultModel": "gpt-4o-mini"},
"configured_secret_fields": ["apiKey"],
"enabled": true,
"last_test_status": "untested",
"last_test_at": ""
}
On update, omit a credential or send an empty value to retain the stored credential. Send a new non-empty value to rotate it. OpenAI and Anthropic tests perform a non-mutating request to the provider's models endpoint; success or failure and its timestamp are persisted on the connector. Custom provider URLs must use HTTPS and cannot resolve to private or reserved network addresses.
UI Config
Endpoints that return presentation content for the web frontend. Marketing and navigation are public; dashboard requires authentication. Settings reads domain APIs directly rather than a static UI payload.
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}"
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