Back to docs
Backend API

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

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 successfully
  • 400 - Invalid email format
  • 401 - 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 out
  • 401 - 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 - Success
  • 401 - 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 - Success
  • 401 - 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 successfully
  • 400 - Invalid flow specification
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
limitintegerMax results per page (default: 20, max: 100)
next_tokenstringPagination 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 - Success
  • 401 - Unauthorized
  • 403 - 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 created
  • 200 - Deployment updated
  • 400 - Invalid deployment configuration
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
flow_namestringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
flow_namestringThe 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
flow_namestringThe flow name
flow_versionstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
flow_namestringThe flow name

Response (200):

{
  "status": "deleted",
  "flow_name": "my-flow"
}

Status Codes:

  • 200 - Flow deleted
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
environmentstringFilter by environment name
limitintegerMax 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 - Success
  • 400 - Invalid limit
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
deployment_namestringThe 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 successful
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
deployment_namestringThe 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 changed
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
deployment_namestringThe 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 found
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
deployment_namestringThe 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"]
}
FieldTypeDescription
execution_modestringRuntime tier: "nano", "micro", "small", "medium", "large", or "xlarge". Determines runtime tier and compute resources.
default_executorstringBackend override: "lambda", "flow-executor", "step-functions", "ecs", or "" (auto).
schedulestringCron expression (5 fields) or interval (e.g. "30m"). Empty string removes the schedule.
tagsobjectKey-value tags. Replaces all existing tags.
dep_package_slugsstring[]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 updated
  • 400 - No settings fields provided, or invalid execution_mode
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
deployment_namestringThe deployment name

Response (200):

{
  "status": "deleted",
  "deployment_name": "my-flow-prod"
}

Status Codes:

  • 200 - Deployment deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 queued
  • 400 - Invalid parameters or flow version
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - Flow or deployment not found
  • 429 - 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:

ParameterTypeDescription
flow_namestringFilter by flow name
statusstringFilter by status (queued, running, completed, failed)
limitintegerMax results (default: 20, max: 100)
next_tokenstringPagination 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
run_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
run_idstringThe unique run identifier

Response (200):

{
  "run_id": "run_789ghi",
  "status": "cancelled",
  "cancelled_at": "2026-03-02T10:20:00Z"
}

Status Codes:

  • 200 - Run cancelled successfully
  • 400 - Run cannot be cancelled (already completed or failed)
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
run_idstringThe unique run identifier

Query Parameters:

ParameterTypeDescription
next_tokenstringPagination token for next page of logs
limitintegerMax 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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: Requires cron_expression (5-field format)
  • interval: Requires interval_seconds
  • one_time: Requires start_at
  • manual: 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 created
  • 400 - Invalid schedule configuration or cron expression
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
flow_namestringFilter by flow name
modestringFilter by mode (cron, interval, one_time, manual)
enabledbooleanFilter by enabled status
limitintegerMax 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
schedule_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
schedule_idstringThe 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 successfully
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - Schedule not found
  • 429 - 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:

ParameterTypeDescription
schedule_idstringThe unique schedule identifier

Request Body:

{
  "enabled": false,
  "cron_expression": "0 6 * * *",
  "timezone": "Europe/London",
  "description": "Updated morning run"
}

All fields are optional:

FieldTypeDescription
enabledbooleanEnable or disable the schedule
timezonestringIANA timezone
catchup_policystringskip, all, or none
cron_expressionstring5-field cron expression
interval_secondsintegerInterval in seconds
one_time_atstringISO 8601 timestamp for one-time schedules
start_atstringSchedule start time
end_atstringSchedule end time
parametersobjectRun parameters
descriptionstringSchedule description
environmentstringTarget 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 updated
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 scopes
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
key_idstringThe unique API key identifier

Response (204): No content

Status Codes:

  • 204 - Key deleted successfully
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 created
  • 400 - Invalid org name or plan
  • 401 - Unauthorized
  • 409 - 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 - Success
  • 401 - 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:

ParameterTypeDescription
org_idstringThe 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/added
  • 400 - Invalid email or role
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - Organization not found
  • 409 - 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:

ParameterTypeDescription
org_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
org_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
org_idstringThe organization identifier
emailstringThe 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 updated
  • 400 - Invalid role
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 created
  • 400 - Invalid canvas JSON
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
draft_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
draft_idstringThe 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 updated
  • 400 - Invalid canvas JSON
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
draft_idstringThe draft identifier

Response (204): No content

Status Codes:

  • 204 - Draft deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
start_datestringStart date (ISO 8601)
end_datestringEnd 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 - Success
  • 400 - Invalid date format
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
start_datestringStart date (ISO 8601)
end_datestringEnd 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 - Success
  • 400 - Invalid date format
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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 created
  • 400 - Invalid plan or URLs
  • 401 - Unauthorized
  • 403 - 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 received
  • 400 - Invalid signature
  • 403 - 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 created
  • 400 - Invalid return URL
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
resource_typestringFilter by resource type (flow, run, schedule, api_key, etc.)
actionstringFilter by action (create, update, delete, execute)
actor_emailstringFilter by actor email
start_datestringStart date (ISO 8601)
end_datestringEnd date (ISO 8601)
limitintegerMax 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 - Success
  • 400 - Invalid query parameters
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
resource_typestringType of resource (flow, run, schedule, api_key)
resource_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 created
  • 400 - Invalid secret name or value
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 409 - 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:

ParameterTypeDescription
environmentstringFilter 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
secret_namestringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
secret_namestringThe secret name

Response (200):

{
  "secret_name": "api-key-prod",
  "value": "sk_live_1234567890abcdefghijklmnop"
}

Status Codes:

  • 200 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
secret_namestringThe 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 updated
  • 400 - Invalid value
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
secret_namestringThe secret name

Response (204): No content

Status Codes:

  • 204 - Secret deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 webhook
  • email: Email alerts
  • webhook: Custom HTTP webhook
  • pagerduty: 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 created
  • 400 - Invalid channel configuration
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
channel_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
channel_idstringThe channel identifier

Response (204): No content

Status Codes:

  • 204 - Channel deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 fails
  • on_success: When run succeeds
  • on_sla_breach: When run exceeds SLA
  • on_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 created
  • 400 - Invalid rule configuration
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
rule_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
rule_idstringThe rule identifier

Response (204): No content

Status Codes:

  • 204 - Rule deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
rule_idstringThe rule identifier

Response (200):

{
  "status": "sent",
  "message": "Test notification sent to 1 channel"
}

Status Codes:

  • 200 - Test notification sent
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 created
  • 400 - Invalid configuration
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 409 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
env_namestringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
env_namestringThe 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 updated
  • 400 - Invalid configuration
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
env_namestringThe environment name

Response (204): No content

Status Codes:

  • 204 - Environment deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
env_namestringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
env_namestringThe 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 replaced
  • 400 - variables must be a JSON object
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
env_namestringThe 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 merged
  • 400 - variables must be a JSON object
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
env_namestringThe environment name
var_keystringThe variable key to delete

Response (200):

{
  "deleted": "LOG_LEVEL",
  "env_name": "staging"
}

Status Codes:

  • 200 - Variable deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 updated
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
env_namestringSource environment name
target_envstringTarget 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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"
}
FieldTypeRequiredDescription
source_deploymentstringYesDeployment name to promote from
target_environmentstringYesTarget environment name
new_deployment_namestringNoOverride deployment name in target
flow_versionstringNoOverride 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 promoted
  • 400 - Missing required fields or validation error
  • 401 - Unauthorized
  • 403 - 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 events
  • webhook: 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 created
  • 400 - Invalid configuration
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
sensor_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
sensor_idstringThe sensor identifier

Response (204): No content

Status Codes:

  • 204 - Sensor deleted
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
sensor_idstringThe sensor identifier

Response (200):

{
  "status": "success",
  "message": "Test event triggered successfully",
  "run_id": "run_901jkl"
}

Status Codes:

  • 200 - Test successful
  • 400 - Test failed (check sensor config)
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 404 - 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:

ParameterTypeDescription
tokenstringThe 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 accepted
  • 400 - Invalid payload
  • 404 - Webhook token not found or sensor disabled
  • 429 - 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
}
FieldTypeRequiredDescription
flow_namestringYesThe flow name
flow_versionstringYesThe flow version
deployment_namestringYesDeployment name
file_sizeintegerYesTotal file size in bytes
part_sizeintegerNoPart 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 initiated
  • 401 - Unauthorized
  • 403 - Insufficient permissions
  • 500 - 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 registered
  • 400 - Failed to complete upload
  • 401 - Unauthorized
  • 403 - 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 aborted
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - Unauthorized
  • 403 - 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 - Success
  • 401 - 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 - Success
  • 401 - Unauthorized
  • 403 - Insufficient permissions

Example:

curl -X GET https://api.dagy.io/v1/ui/ai-studio \
  -H "Authorization: Bearer {token}"

Session Endpoints

EndpointDescription
GET /ai-studio/tiersList available model tiers, limits, and credit balance
POST /ai-studio/sessionsCreate a new AI Studio session
GET /ai-studio/sessionsList 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-flowBind 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}/archiveArchive a session
POST /ai-studio/sessions/{session_id}/messagesSend a synchronous message turn
POST /ai-studio/sessions/{session_id}/messages/streamStream a message turn as SSE
PATCH /ai-studio/sessions/{session_id}/canvasPersist canvas edits
PATCH /ai-studio/sessions/{session_id}/tierChange the model tier for a session
PATCH /ai-studio/sessions/{session_id}/titleRename a session
POST /ai-studio/sessions/{session_id}/attachments/initiateInitiate a single-part attachment upload
POST /ai-studio/sessions/{session_id}/attachments/completeComplete 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"
}
FieldTypeRequiredDescription
messagestringYesNatural language request (max 8000 chars)
intentstringNoOne of auto, build, modify, or advisory
turn_idstringNoStable 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 turn
  • 422 - build or modify request did not produce a valid workflow
  • 400 - Invalid request body
  • 402 - Insufficient credits
  • 404 - Session not found
  • 409 - Duplicate turn or idempotent replay
  • 429 - Session turn or attachment limit reached
  • 401 - Unauthorized
  • 403 - 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_ingest
  • s3_write
  • http_fetch
  • schema_validator
  • data_normalizer
  • deduplicator
  • python_function
  • webhook_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:

ParameterTypeDescription
statusstringFilter by status (active, suspended)
planstringFilter by plan (free, pro, enterprise)
searchstringSearch 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
org_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - Super admin access required
  • 404 - 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:

ParameterTypeDescription
org_idstringThe 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 updated
  • 400 - No fields to update
  • 401 - Unauthorized
  • 403 - Super admin access required
  • 404 - 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:

ParameterTypeDescription
org_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
org_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
org_idstringThe organization identifier

Query Parameters:

ParameterTypeDescription
limitintegerMax 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
org_idstringThe 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 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
org_idstringThe organization identifier

Request Body: None

Response (200):

{
  "status": "suspended",
  "org_id": "org_456def"
}

Status Codes:

  • 200 - Organization suspended
  • 401 - Unauthorized
  • 403 - Super admin access required
  • 404 - 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:

ParameterTypeDescription
org_idstringThe organization identifier

Request Body: None

Response (200):

{
  "status": "active",
  "org_id": "org_456def"
}

Status Codes:

  • 200 - Organization activated
  • 401 - Unauthorized
  • 403 - Super admin access required
  • 404 - 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:

ParameterTypeDescription
org_idstringThe 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 adjusted
  • 400 - Invalid amount
  • 401 - Unauthorized
  • 403 - Super admin access required
  • 404 - 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:

ParameterTypeDescription
org_idstringThe organization identifier
user_emailstringThe user's email address

Response (200):

{
  "status": "revoked",
  "user_email": "dev@acme.com",
  "deleted": 2
}

Status Codes:

  • 200 - Sessions revoked
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
limitintegerMax results (default: 100)

Response (200):

{
  "items": [...]
}

Status Codes:

  • 200 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
keystringThe exception trace key

Response (200): Trace content object.

Status Codes:

  • 200 - Success
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
keystringThe exception trace key

Response (200):

{
  "download_url": "https://s3.amazonaws.com/..."
}

Status Codes:

  • 200 - Success
  • 401 - Unauthorized
  • 403 - 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 deleted
  • 401 - Unauthorized
  • 403 - 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:

ParameterTypeDescription
org_idstringThe organization to impersonate

Request Body: None

Response (200):

{
  "org_id": "org_456def",
  "org_name": "Acme Corporation",
  "impersonating": true
}

Status Codes:

  • 200 - Impersonation started
  • 401 - Unauthorized
  • 403 - Super admin access required
  • 404 - 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 - Healthy
  • 503 - 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 - Success
  • 401 - Unauthorized
  • 503 - 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

StatusMeaning
200Success
201Created
202Accepted (async operation)
204No Content
400Bad Request / Validation Error
401Unauthorized
403Forbidden (insufficient permissions)
404Not Found
409Conflict (e.g., resource already exists)
429Rate Limited or Quota Exceeded
500Internal Server Error
503Service 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 definitions
  • flows.write - Create/update flows
  • flows.delete - Delete flows
  • runs.read - View run history
  • runs.trigger - Trigger new runs
  • runs.cancel - Cancel running runs
  • admin.api_keys - Manage API keys
  • admin.members - Manage organization members
  • admin.audit - View audit logs
  • billing.read - View billing and usage
  • billing.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