Skip to content

API Service

The Stardag API service provides a REST API for task tracking and coordination.

Overview

The API enables:

  • Task registration and status tracking
  • Build coordination across workers
  • Target root configuration
  • Workspace and environment management

Authentication

API Keys

For programmatic access (CI/CD, scripts):

export STARDAG_API_KEY=sk_your_api_key_here

Generate keys from the Web UI under Workspace Settings > API Keys.

OAuth/OIDC

For interactive use (CLI, web):

stardag auth login
uv run stardag auth login

Uses browser-based OAuth flow.

Base URL

Environment URL
SaaS https://api.stardag.com
Local dev http://localhost:8000
Self-hosted Your configured domain

SDK Integration

The SDK handles API communication automatically, and uses registry based on your configuration.

Yoy can also pass a registry implementation to the build functions explicitly:

from stardag.registry import APIRegistry

registry = APIRegistry()
sd.build(task, registry=registry)

API Endpoints

Two prefixes, for two different sets of entities:

  • /api/v1 — auth, users, workspaces, environments, members, invites, API keys and target roots. Unchanged by the registry redesign below.
  • /api/v2 — the registry itself: tasks, task instances, plans, deployments, settings and executions. Everything in this section lives under /api/v2.

Health and version

GET /health                     # API status
GET /api/v2/version             # server_version ("dev" from source) and api_version

Neither needs authentication.

Authentication

GET  /.well-known/jwks.json     # JWKS for token verification
GET  /api/v1/auth/config        # Auth configuration
POST /api/v1/auth/exchange      # Exchange refresh token for workspace-scoped access token

User

GET /api/v1/me                  # Current user profile with workspaces
GET /api/v1/me/invites          # Pending workspace invites

Workspaces

POST   /api/v1/workspaces                           # Create workspace
GET    /api/v1/workspaces/{workspace_id}            # Get workspace details
PATCH  /api/v1/workspaces/{workspace_id}            # Update workspace
DELETE /api/v1/workspaces/{workspace_id}            # Delete workspace
GET    /api/v1/workspaces/{workspace_id}/members    # List members

Environments

GET    /api/v1/workspaces/{workspace_id}/environments                    # List environments
POST   /api/v1/workspaces/{workspace_id}/environments                    # Create environment
GET    /api/v1/workspaces/{workspace_id}/environments/{environment_id}   # Get environment
PATCH  /api/v1/workspaces/{workspace_id}/environments/{environment_id}   # Update environment
DELETE /api/v1/workspaces/{workspace_id}/environments/{environment_id}   # Delete environment

Target Roots

GET /api/v1/target-roots             # Get target roots for current environment

Deployments and settings (/api/v2)

The two halves of a build's scope — see Deployments and code versions and Build & Execution.

POST /api/v2/deployments                     # Record a deployment (before the Modal deploy)
POST /api/v2/deployments/{id}/activate       # Mark it live (after the deploy succeeds)
GET  /api/v2/deployments                     # List deployments (?app_name=, ?current=true)
GET  /api/v2/deployments/{id}                # One deployment (is_current marks the app's current one)
GET  /api/v2/settings/{settings_hash}        # Read a stored settings body
PUT  /api/v2/concurrency-limits/{key}        # Create or replace a named limit
DELETE /api/v2/concurrency-limits/{key}      # Remove a named limit
GET  /api/v2/concurrency-limits              # List named limits (?include_holders=true adds current holders)

Builds (/api/v2)

POST   /api/v2/builds                           # Create a build (root_task_ids required)
GET    /api/v2/builds                           # List builds, most recently active first (?status, ?reactive_app_name, ?limit, ?cursor; total, next_cursor)
GET    /api/v2/builds/{build_id}                # Get a build (error_message on a FAILED one)
GET    /api/v2/builds/{build_id}/plans          # Every plan of the build, newest generation first (as GET /plans/{id})
POST   /api/v2/builds/{build_id}/complete       # Complete (recomputes plan_complete)
POST   /api/v2/builds/{build_id}/fail           # Fail
POST   /api/v2/builds/{build_id}/cancel         # Cancel (releases every plan's claims)
POST   /api/v2/builds/{build_id}/exit-early     # Exit without releasing claims
POST   /api/v2/builds/{build_id}/resume         # Resume under a (deployment, settings) scope
DELETE /api/v2/builds/{build_id}                # Delete (refused while a claim or open execution remains)
GET    /api/v2/builds/{build_id}/frontier       # Runnable / discovery-job / running members
GET    /api/v2/builds/{build_id}/events         # The build's event log
GET    /api/v2/builds/{build_id}/executions     # This build's executions (?not_in_current_plan, ?include_ended)
POST   /api/v2/builds/{build_id}/skip-blocked   # Propagate skip-blocked over the active plan

Plans and members (/api/v2)

A plan is one build's request under one scope; a member is one task instance admitted into it. See The plan: roots, discovery, closure.

POST /api/v2/builds/{build_id}/plans                              # Create/reuse a plan; register unexpanded roots
GET  /api/v2/plans/{plan_id}                                       # Lifecycle, scope with the deployment, member counts by status (excluded apart); superseded plans too
GET  /api/v2/plans/{plan_id}/roots                                 # The plan's root instances
GET  /api/v2/plans/{plan_id}/graph                                 # Every member (status, admission, exclusion, attempts, interruptions) and the instance edges between them
POST /api/v2/plans/{plan_id}/members                               # Register a chunk (static or discovery-job result)
POST /api/v2/plans/{plan_id}/seal                                  # Verify and seal (activates a replacement plan)
POST /api/v2/plans/{plan_id}/members/{task_id}/start               # Claiming start
POST /api/v2/plans/{plan_id}/members/{task_id}/complete            # Report completion
POST /api/v2/plans/{plan_id}/members/{task_id}/fail                # Report failure
POST /api/v2/plans/{plan_id}/members/{task_id}/suspend             # Suspend (dynamic dependencies pending)
POST /api/v2/plans/{plan_id}/members/{task_id}/retry               # Reset a failed/cancelled/skipped task
POST /api/v2/plans/{plan_id}/members/{task_id}/interrupt           # Report an interruption (checkpointed)
POST /api/v2/plans/{plan_id}/members/{task_id}/preempt             # Report a platform preemption
POST /api/v2/plans/{plan_id}/members/{task_id}/skip                # Skip (never-started, upstream failed)
POST /api/v2/plans/{plan_id}/members/{task_id}/cancel              # Cancel one task (the claim holder only)
POST /api/v2/plans/{plan_id}/members/{task_id}/yield               # One yield batch: children + closure
POST /api/v2/plans/{plan_id}/members/{task_id}/exclude             # Operator: give up on this member
POST /api/v2/plans/{plan_id}/members/{task_id}/discovery-failed    # Discovery excludes itself (class import / requires())
POST /api/v2/plans/{plan_id}/members/{task_id}/artifacts           # Upload artifacts
POST /api/v2/tasks/{task_id}/claim/renew                           # Renew an in-process claim's TTL

Tasks (/api/v2)

GET /api/v2/tasks                        # Tasks, most recent status change first (?status, ?limit, ?cursor; next_cursor)
GET /api/v2/tasks/{task_id}              # The task: status, claim (claim_plan_id, claim_build_id), current execution, instances
GET /api/v2/tasks/{task_id}/executions   # Its executions across builds, newest first (?include_ended, default true; ?limit)
GET /api/v2/tasks/{task_id}/artifacts    # This task's artifacts
GET /api/v2/tasks/{task_id}/events       # This task's event log, oldest first (?limit, at most 500)

Executions and wake-ups (/api/v2)

POST   /api/v2/executions/{execution_id}/stopped        # Record a stop; releases a still-held claim
POST   /api/v2/builds/wake-candidates                    # Flagged builds with no live scheduler lease
POST   /api/v2/builds/{build_id}/notify                  # Flag a build for a wake-up
GET    /api/v2/builds/{build_id}/notify                  # Read the wake flag
DELETE /api/v2/builds/{build_id}/notify                  # Clear the wake flag
POST   /api/v2/builds/{build_id}/scheduler-lease         # Acquire the scheduler lease
PUT    /api/v2/builds/{build_id}/scheduler-lease         # Renew it
DELETE /api/v2/builds/{build_id}/scheduler-lease         # Release it
PUT    /api/v2/builds/{build_id}/reactive-meta           # Set the owning app + tick config
POST   /api/v2/builds/{build_id}/tick-summaries          # Record a tick's outcome
GET    /api/v2/builds/{build_id}/tick-summaries          # List recent ticks

Refusal codes

A write route that cannot apply refuses with a 4xx and a detail naming one of these reasons — never a silent no-op and never a 500 for an ordinary race. This list is not exhaustive: it covers the codes a client building a plan and reporting task progress will hit; deployment, execution-ledger and settings routes add a few more of their own (see below the table).

Code Status Meaning
task_identity_conflict 409 An existing task row disagrees on namespace/name/version/output_uri
instance_body_conflict 409 The scope already has this instance_hash with a different body
instance_conflict 409 The plan already holds a different instance of this task id
root_instance_conflict 409 A re-trigger's roots differ from the build's recorded ones
root_mismatch 400 Plan roots do not match the build's root_task_ids
duplicate_item 400 One instance appears twice in a chunk with different items
unknown_upstream_instance 400 A declared upstream is not a registered instance in this scope
unknown_yielded_instance 400 A yielded hash was not one of the batch's own items
plan_sealed 409 A non-idempotent write against an already-sealed plan
plan_incomplete_registration 409 /seal found unexpanded or missing members
plan_incomplete 409 /complete found the plan unsealed, incomplete or an excluded root (the latter carries detail.reason = "root_excluded", not a code of its own)
plan_superseded 409 The plan is not the build's active one
member_excluded 409 A claiming start named an excluded member
not_expanded 409 A claiming start named a member with no known upstreams yet
upstream_incomplete 409 Re-checked at claim time: an upstream is not COMPLETED
task_not_actionable 409 The task's status is not one a claim may be taken from
task_already_completed 409 An observation raced a claiming start; the task is already COMPLETED
task_not_skippable 409 skip against a result (COMPLETED, FAILED, CANCELLED) it must not overwrite
not_claim_holder 409 A report or single-task action came through a plan that is not the claim holder
build_not_running 409 A claiming start against a build that is not RUNNING
build_terminal 409 complete/fail/cancel/exit-early on a COMPLETED, FAILED or CANCELLED build; recorded, not applied (resume is the way out)
deployment_mismatch 409 A worker's STARDAG_DEPLOYMENT_ID differs from its plan's
deployment_activation_conflict 409 /activate disagrees with a value already recorded
local_deployment_conflict 409 A local lookup names another app for a recorded code id
unknown_limit 404 A concurrency limit key does not exist
concurrency_limit_reached 409 A named limit is full at claim time
reserved_settings_key 400 A settings key starts STARDAG_ or MODAL_
clock_skew 400 An observed_at is ahead of the server's clock by too much
rate_limited 429 Per-workspace write rate limit (Retry-After header)
creation_quota_exceeded 429 The 24-hour task_instance creation quota for the environment

The deployment, execution-ledger and settings routes add a few more:

Code Status Meaning
unknown_deployment 400/404 A plan or activation names a deployment id that does not exist
deployment_not_activated 400 A plan's deployment has not been activated yet
deployment_not_current 409 A rollover's deployment is no longer the app's current one
build_has_live_work 409 A build delete finds a live claim or an unreported execution end
execution_not_current 409 A report or end targets a claim already released, taken over or ended
unknown_execution 404/409 A report or stop names an execution id that does not exist for the task
invalid_settings 400 A settings body is not a flat mapping of strings to strings

Two of these vary by route: unknown_deployment is a plain 400 at plan creation but a 404 from the activation lookup; unknown_execution is a 404 from builds stop's report-stopped route but a 409 (recorded, since the attempt is logged before the refusal) from a task's own progress report.

Error Handling

API errors return JSON with:

{
  "detail": "Error description"
}

Common status codes:

Code Meaning
401 Invalid or expired authentication
403 Insufficient permissions
404 Resource not found
409 Conflict — see the table above
422 Validation error
429 Rate or quota limited

See Also