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):
Generate keys from the Web UI under Workspace Settings > API Keys.
OAuth/OIDC¶
For interactive use (CLI, web):
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:
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¶
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¶
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:
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¶
- Using the API Registry - SDK integration guide
- Self-Hosting - Run your own API server