Skip to content

Plans API

Plans are the review and deployment mechanism for schema changes. When dagctl detects new commits in your repository, it triggers an explain step that shows what will change. A human then approves or rejects that plan. Approval triggers an execute step that applies the changes.

Both SQLMesh and dbt projects use the same plan endpoints. The internal structure of a plan object differs slightly between the two (see response examples below).

Plan Lifecycle

running (explain)  ->  pending (awaiting approval)  ->  approved
                                                              |
                                                         running (execute)  ->  completed
                                                                            ->  failed  ->  (retry)
                       rejected / cancelled

A plan that is auto-discarded because the repository changed since the explain was created will have status: "rejected" with auto_discarded: true in the plan_output.


GET /api/v1/projects/:id/plans

Returns a paginated list of plans for a project.

Path Parameters

Parameter Type Description
id string Project ID

Query Parameters

Parameter Type Default Description
page integer 1 Page number
limit integer 25 Results per page
status string - Filter by status: pending, running, completed, failed, rejected, cancelled

Response

{
  "plans": [
    {
      "id": "plan_abc123",
      "plan_id": "plangroup_xyz",
      "plan_type": "explain",
      "project_id": "proj_xyz",
      "from_commit_sha": "a1b2c3d",
      "to_commit_sha": "f6e5d4c",
      "commit_message": "Add orders model",
      "commits_behind": 1,
      "status": "pending",
      "triggered_by": "manual",
      "created_at": "2026-04-28T10:00:00Z",
      "updated_at": "2026-04-28T10:02:30Z"
    }
  ],
  "total": 42,
  "page": 1,
  "limit": 25,
  "total_pages": 2
}

GET /api/v1/projects/:id/plans/pending

Returns the count of plans currently awaiting approval.

Path Parameters

Parameter Type Description
id string Project ID

Response

{
  "pending_count": 2
}

POST /api/v1/projects/:id/plans

Manually triggers a new explain plan for a project. dagctl resolves the current branch HEAD to a commit SHA, creates an explain record, and launches a Kubernetes job to compute the diff.

If a plan already exists for the same commit, the existing plan is returned instead of creating a duplicate.

Path Parameters

Parameter Type Description
id string Project ID

Request Body

No body required. The plan is always created against the current branch HEAD.

Response

{
  "message": "Plan created and explain job triggered",
  "plan_id": "plan_abc123",
  "plan_group_id": "plangroup_xyz",
  "job_name": "analytics-plan-explain-20260428-100000",
  "status": "running",
  "initial_status": "running"
}

If the commit is already deployed:

{
  "message": "Commit a1b2c3d is already deployed",
  "status": "already_deployed",
  "to_commit_sha": "a1b2c3d"
}

If a plan already exists for the commit:

{
  "message": "A plan already exists for commit a1b2c3d",
  "status": "existing_plan",
  "plan_id": "plan_abc123",
  "plan_group_id": "plangroup_xyz",
  "current_status": "pending"
}

GET /api/v1/plans/:planId

Returns a single plan by ID. Works for both SQLMesh and dbt plan records.

Path Parameters

Parameter Type Description
planId string Plan ID

Response (SQLMesh plan)

{
  "id": "plan_abc123",
  "plan_id": "plangroup_xyz",
  "plan_type": "explain",
  "project_id": "proj_xyz",
  "from_commit_sha": "a1b2c3d",
  "to_commit_sha": "f6e5d4c",
  "commit_message": "Add orders model",
  "commits_behind": 1,
  "status": "pending",
  "job_name": "analytics-plan-explain-20260428-100000",
  "plan_output": {
    "environment": "prod",
    "is_new_environment": false,
    "modelsAdded": 1,
    "modelsModified": 2,
    "modelsRemoved": 0,
    "directly_modified": ["orders.stg_orders"],
    "indirectly_modified": ["orders.fct_orders"],
    "modelsNeedingBackfill": 1,
    "explain_completed": true
  },
  "triggered_by": "manual",
  "created_at": "2026-04-28T10:00:00Z",
  "updated_at": "2026-04-28T10:02:30Z"
}

GET /api/v1/plans/group/:planGroupId/steps

Returns all steps (explain and execute) for a plan group. Use this to show the full plan timeline.

Path Parameters

Parameter Type Description
planGroupId string The shared plan_id field that links explain and execute steps

Response

{
  "plan_group_id": "plangroup_xyz",
  "project_id": "proj_xyz",
  "steps": [
    {
      "id": "plan_abc123",
      "plan_type": "explain",
      "status": "approved",
      ...
    },
    {
      "id": "plan_def456",
      "plan_type": "execute",
      "status": "completed",
      ...
    }
  ],
  "current_status": "completed"
}

For running plans, logs in each step is populated with live output from Kubernetes rather than the stored database value.


GET /api/v1/plans/:planId/logs

Returns logs for a plan step. For running plans, logs are fetched live from Kubernetes. For completed or failed plans, stored logs are returned.

Path Parameters

Parameter Type Description
planId string Plan ID

Response

{
  "planId": "plan_abc123",
  "logs": "...",
  "status": "completed",
  "jobName": "analytics-plan-execute-20260428-100500",
  "podName": "analytics-plan-execute-20260428-100500-xk9vz"
}

POST /api/v1/plans/:planId/approve

Approves a pending explain plan and launches the execute step. Before approving, dagctl re-validates that the repository HEAD has not changed since the explain was created. If it has, the plan is auto-discarded and the response will indicate auto_discarded: true.

Requires the approve_plan permission.

Path Parameters

Parameter Type Description
planId string Plan ID (must be an explain step in pending status)

Response

{
  "message": "Plan approved and execution started",
  "explain_plan_id": "plan_abc123",
  "execution_plan_id": "plan_def456",
  "plan_group_id": "plangroup_xyz",
  "job_name": "analytics-plan-execute-20260428-100500"
}

If the repository changed since the explain was created:

{
  "message": "Plan auto-discarded due to repository changes",
  "plan_id": "plan_abc123",
  "status": "auto_discarded",
  "auto_discarded": true,
  "expected_commit": "f6e5d4c",
  "current_commit": "9z8y7x6",
  "reason": "Repository has new commits. Please create a new plan to review the latest changes."
}

POST /api/v1/plans/:planId/reject

Rejects a pending plan. The plan is marked as rejected and no execution occurs.

Path Parameters

Parameter Type Description
planId string Plan ID (must be in pending status)

Response

{
  "message": "Plan rejected successfully",
  "plan_id": "plan_abc123"
}

POST /api/v1/plans/:planId/cancel

Cancels a running or approved plan. The Kubernetes job is deleted and the plan is marked as cancelled. Partial logs captured at the time of cancellation are stored.

Path Parameters

Parameter Type Description
planId string Plan ID (must be in running or approved status)

Response

{
  "message": "Plan cancelled successfully",
  "plan_id": "plan_abc123"
}

POST /api/v1/plans/:planId/retry

Retries a failed or cancelled execution plan. The commit SHA is re-validated against the current branch HEAD before the retry proceeds. Maximum 3 retries per plan group (4 total executions).

Path Parameters

Parameter Type Description
planId string Plan ID (must be an execute step in failed or cancelled status)

Response

{
  "message": "Plan retry started",
  "execution_plan_id": "plan_ghi789",
  "retry_of": "plan_def456"
}

DELETE /api/v1/plans/:planId

Deletes a pending plan. Only plans in pending status can be deleted.

Path Parameters

Parameter Type Description
planId string Plan ID

Response

{
  "message": "Plan deleted successfully"
}