Projects API
These endpoints manage the full lifecycle of dagctl projects — SQLMesh and dbt pipelines connected to a Git repository.
GET /api/v1/projects
Returns all projects in your organization.
Response
{
"projects": [
{
"id": "proj_abc123",
"organization_id": "org-xxxxxxxx",
"name": "analytics",
"description": "Core analytics models",
"project_type": "sqlmesh",
"repository_url": "git@github.com:acme/analytics.git",
"branch": "main",
"git_auth_method": "ssh",
"sqlmesh_environment": "prod",
"sqlmesh_version": "0.228.5",
"use_managed_state_database": true,
"auto_apply": false,
"run_tests": true,
"deployed": true,
"suspended": false,
"status": "active",
"commits_behind": 0,
"current_commit_sha": "a1b2c3d",
"latest_commit_sha": "a1b2c3d",
"latest_commit_message": "Add orders model",
"cpu_request": "500m",
"cpu_limit": "2000m",
"memory_request": "1Gi",
"memory_limit": "4Gi",
"concurrency_policy": "Allow",
"successful_runs_history_limit": 3,
"failed_runs_history_limit": 3,
"created_at": "2026-01-15T10:00:00Z",
"updated_at": "2026-02-20T14:30:00Z"
}
],
"total": 3
}
GET /api/v1/projects/:id
Returns a single project by ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
string | Project ID |
Response
Returns a single project object (same shape as list items above).
POST /api/v1/projects
Creates a new project and auto-deploys it. For SQLMesh projects, dagctl reads the repository to detect gateway names from your config.yaml or config.py.
Request Body
SQLMesh project:
{
"name": "analytics",
"description": "Core analytics models",
"project_type": "sqlmesh",
"repository_url": "https://github.com/acme/analytics",
"branch": "main",
"git_auth_method": "github_app",
"sqlmesh_environment": "prod",
"sqlmesh_version": "0.228.5",
"use_managed_state_database": true,
"auto_apply": false,
"run_tests": true
}
dbt project:
{
"name": "dbt-core",
"project_type": "dbt",
"repository_url": "https://github.com/acme/dbt-project",
"branch": "main",
"git_auth_method": "github_app",
"dbt_version": "1.11.6",
"python_version": "3.11",
"polling_interval": "5m",
"version_strategy": "semantic"
}
Body Fields
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Project name |
project_type |
string | no | "sqlmesh" (default) or "dbt" |
repository_url |
string | yes | Git repository URL |
branch |
string | no | Branch name (default: "main") |
git_auth_method |
string | no | "none", "ssh", or "github_app" |
git_secret_ref |
string | no | Name of the SSH key secret (when git_auth_method is "ssh") |
git_path |
string | no | Subdirectory within the repo containing the project |
description |
string | no | Human-readable description |
sqlmesh_environment |
string | no | SQLMesh environment name (default: "prod") |
sqlmesh_version |
string | no | SQLMesh version. Must be one of the values returned by GET /api/v1/sqlmesh-versions; any other value is rejected with 400. |
use_managed_state_database |
boolean | no | Use dagctl-managed state DB (default: true for SQLMesh) |
auto_apply |
boolean | no | Auto-apply plans. When true, every explain plan for the project is approved and applied automatically as soon as the explain completes — no human review. Default false. See Auto-apply. |
slack_alerts_enabled |
boolean or null |
no | Plan alerting: null (default) inherits the organization default, true enables, false disables. See Plan alerts. |
slack_channel |
string | no | Slack channel for plan alerts (e.g. #data-alerts). Overrides the organization default channel. |
slack_alert_on_success |
boolean | no | Also alert when a plan applies successfully (default: failures only). |
run_tests |
boolean | no | Run tests during execution |
cpu_request |
string | no | Kubernetes CPU request (default: "500m") |
cpu_limit |
string | no | Kubernetes CPU limit (default: "2000m") |
memory_request |
string | no | Kubernetes memory request (default: "1Gi") |
memory_limit |
string | no | Kubernetes memory limit (default: "4Gi") |
concurrency_policy |
string | no | Kubernetes CronJob concurrency policy: "Allow", "Forbid", "Replace" (default: "Allow") |
suspended |
boolean | no | Start the project in suspended state |
agent_context |
string | no | Custom context for the AI agent (max 10,000 characters) |
dbt_version |
string | no | dbt-core version (dbt projects only). Must be one of the values returned by GET /api/v1/dbt-versions; any other value is rejected with 400. |
python_version |
string | no | Python version for dbt build (dbt projects only) |
polling_interval |
string | no | Version polling interval, e.g. "5m", "30s" (dbt projects only, minimum 10s) |
version_strategy |
string | no | "semantic" or "continuous" (dbt projects only) |
Response
Returns 201 Created with the project object.
PUT /api/v1/projects/:id
Updates an existing project. Only supplied fields are changed. If the project is deployed, the ArgoCD Application is updated automatically.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
string | Project ID |
Request Body
All fields are optional. Same fields as create, with these additions:
| Field | Type | Description |
|---|---|---|
auto_apply |
boolean | Auto-apply plans — approve and apply every plan with no human review. See Auto-apply. |
slack_alerts_enabled |
boolean or null |
Plan alerting: send null explicitly to switch back to the organization default. See Plan alerts. |
slack_channel |
string | Slack channel for plan alerts; send "" to clear. |
slack_alert_on_success |
boolean | Also alert on successful applies. |
run_tests |
boolean | Run tests during execution |
suspended |
boolean | Suspend or resume the project |
remediation_auto_merge |
boolean | Automatically merge remediation PRs |
max_concurrent_job_runs |
integer | Maximum concurrent job runs (default: 1). Runs triggered beyond this limit are rejected and recorded as skipped. |
status |
string | Override project status |
Response
Returns 200 OK with the updated project object.
DELETE /api/v1/projects/:id
Deletes a project. If the project is deployed, it is undeployed first via ArgoCD. The managed state database is also deleted if use_managed_state_database is true.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id |
string | Project ID |
Response
POST /api/v1/projects/:id/deploy
Deploys a project by creating or updating its ArgoCD Application.
Response
DELETE /api/v1/projects/:id/deploy
Undeploys a project by removing its ArgoCD Application. The project record is retained.
Response
POST /api/v1/projects/:id/run
Triggers an immediate run using the project's default job configuration. If no job exists for the project, a default one is auto-created. This is the "Run Now" shortcut that does not require a pre-existing job ID.
Response
{
"message": "SQLMesh job run triggered successfully",
"job_run_id": "run_abc123",
"job_id": "job_xyz789",
"job_name": "analytics-scheduled-run-manual-20260428-120000"
}
The message text reflects the project framework — "SQLMesh job run triggered successfully" or "DBT job run triggered successfully". No status field is returned; poll the run to get its status.
Manual runs have their own concurrency limit
This endpoint allows at most 3 active run jobs per project and returns 429 with too_many_active_runs beyond that. This is a separate limit from max_concurrent_job_runs, which applies to scheduled runs and rejects them as skipped.
GET /api/v1/projects/:id/sync-status
Returns the current git sync status for the project.
Response
{
"is_synced": false,
"needs_sync": true,
"current_sha": "a1b2c3d4e5f6",
"latest_sha": "f6e5d4c3b2a1",
"commits_behind": 2,
"last_check": "2026-04-28T12:00:00Z",
"check_age_minutes": 7,
"check_is_stale": false,
"sync_error": ""
}
| Field | Type | Description |
|---|---|---|
is_synced |
boolean | Whether the deployed commit matches the latest commit on the tracked branch |
needs_sync |
boolean | Computed: whether a sync is required |
current_sha |
string | Commit SHA currently deployed |
latest_sha |
string | Latest commit SHA on the tracked branch |
commits_behind |
integer | How many commits the deployment is behind |
last_check |
string | RFC3339 timestamp of the last sync check |
check_age_minutes |
integer | Minutes since the last check. Present only when a check has run. |
check_is_stale |
boolean | Whether the last check is old enough to be unreliable. Present only when a check has run. |
sync_error |
string | Error from the last sync check, empty if none |
Auto-apply
auto_apply turns a project into a continuous-deployment target. With it on:
- Every explain plan that reaches
pendingis approved and applied immediately — plans created by the git sync poller and plans you create manually via the API or the Create and apply plan button. There is no review step and no undo. - Changes deploy to the project's configured environment (
sqlmesh_environment,prodby default), including model removals, restatements and backfills. - If another plan is already executing, the new explain waits (status stays
pending) and is applied automatically when that execution finishes. - The execute step records
triggered_by: "auto_apply"andapproved_by_actor: "system:auto_apply". - Plans that remove models post a warning to the project's Slack alert channel (if configured) but are not blocked.
- Environment protections are not consulted by automatic approvals.
auto_apply defaults to false and must be enabled explicitly per project (the project settings page asks for confirmation). Calling POST /plans/:planId/approve on a plan that auto-apply already approved returns 200 with "status": "already_approved" rather than an error, so approval clients stay idempotent.
Warning
Before this release the field was stored but never read; every plan required manual approval regardless of its value. All existing projects were reset to false when the behaviour went live.
Plan alerts
Plan explain and execute failures (and, optionally, successes) are posted to Slack using the organization's Slack connection (Settings → Integrations). Where the message goes is resolved per project:
slack_alerts_enabled |
Behaviour |
|---|---|
null (default) |
Inherit: alerts are sent if the organization's Send plan alerts default is on. Channel = the project's slack_channel if set, otherwise the organization default channel. |
true |
Always alert. Channel = slack_channel, falling back to the organization default channel. |
false |
Never alert for this project, regardless of organization defaults. |
Organization defaults are managed with GET/PUT /api/v1/organizations/settings/alert-settings:
PUT accepts a partial body and requires the manage_org permission. Job-run alerts that have alerts enabled but no channel of their own fall back to the same default channel.
Each plan alerts at most once (alerts_sent), so retried job-watcher callbacks do not produce duplicates. Cancelling a plan does not alert.