CAIP Workflows API Reference
Overview
The CAIP Workflows API lets teams define, version, stage, configure, and execute machine-learning and data-processing pipelines on the Connected AI Platform. Workflows are persisted in a database (the single source of truth) and translated into Argo Workflow specifications only when a run is created.
- Base URL:
https://workflow.prod.caip.api.orbit.eu-central-1.aws.cloud.bmw/ - Version: 1.0.0
All endpoints are served under a versioned, space-scoped prefix:
/v1/spaces/{spaceId}
spaceId provides multi-tenant isolation and maps to the Kubernetes namespace at run time.
For working, forkable calls against every endpoint described here, see the example repository.
Authentication
All endpoints require authentication using a JWT bearer token or M2M token in the Authorization header:
Authorization: Bearer <your-jwt-token>
Error Handling
Errors share a single JSON shape:
{ "detail": "Human-readable explanation of the error" }
The 422 validation error for unresolved configuration references uses a list of objects instead:
{ "detail": [ { "msg": "Unresolved configuration reference: ${config.BUCKET}" } ] }
Common HTTP Status Codes
| Status Code | Description |
|---|---|
200 | OK - Successful read, update, or idempotent no-op |
201 | Created - Resource created (or upserted for the first time) |
204 | No Content - Successful delete |
400 | Bad Request - Invalid input (e.g. unknown stage, malformed identifier) |
401 | Unauthorized - Invalid or missing webhook signature |
404 | Not Found - Resource (or a referenced parent) does not exist |
409 | Conflict - Operation blocked by state (e.g. deleting a version with active runs, or a configuration still bound to a stage) |
422 | Unprocessable Entity - A ${config.KEY} reference could not be resolved at run creation |
500 | Internal Server Error - An error occurred on the server |
Custom Method (colon) Verbs
Some actions use the Google AIP-style custom-method syntax, where a verb is appended to a resource with a colon — e.g. …/versions:validate, …/runs/{runId}:cancel, …/runs/{runId}:retry, …/triggers/{triggerId}:invoke, …/triggers/{triggerId}:rotate-secret.
Idempotency
Run creation and retry accept an optional Idempotency-Key header to de-duplicate submissions. Upserts (PUT) are inherently idempotent.
Pagination
List endpoints that support pagination use cursor-based parameters:
pageSize— integer, 1–100 (default 20).pageToken— opaque cursor returned asnextPageTokenin the previous page.
Table of Contents
Endpoints
Workflows
Endpoints for managing workflows within a space.
Create or Update a Workflow
- Idempotent upsert of a workflow.
- Endpoint:
PUT /v1/spaces/{spaceId}/workflows/{workflowId} - Authentication: Required
Request Body:
{
"name": "ML Training Pipeline"
}
Response:
{
"spaceId": "space_12345",
"workflowId": "ml-training-pipeline",
"name": "ML Training Pipeline",
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T14:00:00.000Z"
}
Example Request:
curl -X PUT "https://workflows.api.caip.bmw.cloud/v1/spaces/space_12345/workflows/ml-training-pipeline" \
-H "Authorization: Bearer <your-jwt-token>" \
-H "Content-Type: application/json" \
-d '{ "name": "ML Training Pipeline" }'
Response Codes:
201: Workflow created or upserted400: Invalid identifier
List Workflows
- Endpoint:
GET /v1/spaces/{spaceId}/workflows - Authentication: Required
Response:
{
"items": [
{
"spaceId": "space_12345",
"workflowId": "ml-training-pipeline",
"name": "ML Training Pipeline",
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T14:00:00.000Z"
}
],
"totalCount": 1
}
Get a Workflow
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId} - Authentication: Required
Response Codes:
200: Workflow returned404: Workflow not found
Delete a Workflow
- Endpoint:
DELETE /v1/spaces/{spaceId}/workflows/{workflowId} - Authentication: Required
Response Codes:
204: Workflow deleted409: Workflow has active runs
Workflow Versions
A workflow version captures the task DAG of a workflow at a point in time. Task arguments values may embed ${config.KEY} placeholders, resolved at run time from the effective configuration.
Each task's templateRef points to a platform-provided task template by name and version. The following templates are available:
Template (templateRef.name) | Version | Purpose |
|---|---|---|
fetch-git-code | v1 | Clone a Git repository from BMW code hosting into the shared workspace. |
fetch-git-code-atc | v1 | Variant of fetch-git-code that authenticates with the ATC GitHub App credentials. |
run-python-code | v1 | Run a Python script in a container (single node, no Ray cluster). Chain after fetch-git-code. |
submit-ray-job | v1 | Submit a distributed Ray job to a Ray cluster. |
The Version column shows
v1, a floating alias that always resolves to the latestv1.x.xrelease of the template. See Template versioning (SemVer) for the other reference forms.
Template versioning (SemVer)
Task templates follow Semantic Versioning. A templateRef.version accepts exactly three forms:
| Form | Example | Meaning |
|---|---|---|
vMAJOR | v1 | Floating alias — always resolves to the latest vMAJOR.*.* release. |
vMAJOR.MINOR | v1.2 | Floats to the latest patch within that minor. |
vMAJOR.MINOR.PATCH | v1.2.3 | Immutable pin — an exact release, for reproducible runs. |
Every task-template version is v-prefixed; there is no bare form. A bare 1.2.3 (without the v) is rejected.
When a template publisher cuts a new version, the bump signals intent: MAJOR is a breaking change (consumers must opt in explicitly), MINOR adds backward-compatible functionality (e.g. a new optional argument), and PATCH is a backward-compatible bug fix.
Create or Update a Version
- Idempotent upsert of the version's task DAG.
- Endpoint:
PUT /v1/spaces/{spaceId}/workflows/{workflowId}/versions/{versionId} - Authentication: Required
Request Body:
{
"tasks": [
{
"name": "fetch-git-code",
"templateRef": { "name": "fetch-git-code", "version": "v1" },
"arguments": {
"git-url": "git@github.com:example/repo.git",
"git-branch": "main"
},
"dependencies": []
},
{
"name": "ray-training",
"templateRef": { "name": "submit-ray-job", "version": "v1" },
"arguments": {
"entrypoint": "train.py",
"output-bucket": "s3://${config.BUCKET}/output",
"worker-replicas": "${config.RAY_REPLICAS}"
},
"dependencies": ["fetch-git-code"]
}
]
}
argumentsis a flat string-to-string map. Values may contain${config.KEY}references resolved at run time.
Response:
{
"workflowId": "ml-training-pipeline",
"versionId": "1.0.0",
"tasks": [ /* ... */ ],
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T14:00:00.000Z"
}
Response Codes:
201: Version upserted404: Parent workflow not found
Validate a Version
- Validates a task DAG without persisting it.
- Endpoint:
POST /v1/spaces/{spaceId}/workflows/{workflowId}/versions:validate - Authentication: Required
Request Body:
{ "tasks": [ /* same shape as create */ ] }
Response:
{
"valid": false,
"errors": ["Task 'ray-training' references unknown dependency 'fetch'"]
}
List Versions
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/versions - Authentication: Required
- Supports pagination (
pageSize,pageToken).
Get a Version
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/versions/{versionId} - Authentication: Required
Response Codes:
200: Version returned404: Version not found
Delete a Version
- Deletes the version and also removes any stage assignment that references it.
- Endpoint:
DELETE /v1/spaces/{spaceId}/workflows/{workflowId}/versions/{versionId} - Authentication: Required
Response Codes:
204: Version deleted409: Version has active (non-terminal) runs
Stage Assignments
A stage assignment promotes a specific version into a stage (test, int, e2e, prod) and optionally binds a configuration to it.
Assign a Version to a Stage
- Endpoint:
PUT /v1/spaces/{spaceId}/workflows/{workflowId}/stages/{stage} - Authentication: Required
Request Body:
{
"versionId": "1.0.0",
"configurationId": "prod-config"
}
configurationIdis optional. When set, runs and triggers on this stage resolve${config.KEY}references from that configuration.
Response:
{
"workflowId": "ml-training-pipeline",
"stage": "prod",
"versionId": "1.0.0",
"configurationId": "prod-config",
"assignedAt": "2025-06-18T14:00:00.000Z",
"assignedBy": null
}
Response Codes:
201: New assignment created200: Existing assignment updated404: ReferencedversionIdorconfigurationIddoes not exist400: Invalid stage
Get a Stage Assignment
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/stages/{stage} - Authentication: Required
Response Codes:
200: Assignment returned404: No version assigned to the stage
List Stage Assignments
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/stages - Authentication: Required
Response:
{
"items": [
{
"workflowId": "ml-training-pipeline",
"stage": "prod",
"versionId": "1.0.0",
"configurationId": "prod-config",
"assignedAt": "2025-06-18T14:00:00.000Z",
"assignedBy": null
}
],
"totalCount": 1
}
Delete a Stage Assignment
- Endpoint:
DELETE /v1/spaces/{spaceId}/workflows/{workflowId}/stages/{stage} - Authentication: Required
Response Codes:
204: Assignment removed
Configurations
A configuration is a named, flat key/value map (string → string) belonging to a workflow. Workflow version task arguments reference configuration values with ${config.KEY} syntax, resolved at run time. The same version can therefore run across all stages — only the bound configuration changes.
Create or Update a Configuration
- Idempotent upsert of the configuration's
datamap. - Endpoint:
PUT /v1/spaces/{spaceId}/workflows/{workflowId}/configurations/{configurationId} - Authentication: Required
Request Body:
{
"data": {
"BUCKET": "prod-bucket",
"MODEL_PATH": "/models/v3",
"RAY_REPLICAS": "4"
}
}
Response:
{
"configurationId": "prod-config",
"workflowId": "ml-training-pipeline",
"data": {
"BUCKET": "prod-bucket",
"MODEL_PATH": "/models/v3",
"RAY_REPLICAS": "4"
},
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T14:00:00.000Z"
}
Example Request:
curl -X PUT "https://workflows.api.caip.bmw.cloud/v1/spaces/space_12345/workflows/ml-training-pipeline/configurations/prod-config" \
-H "Authorization: Bearer <your-jwt-token>" \
-H "Content-Type: application/json" \
-d '{ "data": { "BUCKET": "prod-bucket", "RAY_REPLICAS": "4" } }'
Response Codes:
201: Configuration created200: Existing configuration replaced
Get a Configuration
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/configurations/{configurationId} - Authentication: Required
Response Codes:
200: Configuration returned404: Configuration not found
List Configurations
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/configurations - Authentication: Required
Response:
{
"items": [
{
"configurationId": "prod-config",
"workflowId": "ml-training-pipeline",
"data": { "BUCKET": "prod-bucket", "MODEL_PATH": "/models/v3", "RAY_REPLICAS": "4" },
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T14:00:00.000Z"
}
],
"totalCount": 1
}
Delete a Configuration
- Endpoint:
DELETE /v1/spaces/{spaceId}/workflows/{workflowId}/configurations/{configurationId} - Authentication: Required
Response Codes:
204: Configuration deleted409: Configuration still referenced by a stage assignment (unbind it from all stages first)
Runs
A run is a single execution of a workflow version in a stage, backed by an Argo Workflow.
Create a Run
- Triggers execution of a workflow version in a stage.
versionIdandstageare query parameters. - Endpoint:
POST /v1/spaces/{spaceId}/workflows/{workflowId}/runs?versionId={versionId}&stage={stage} - Authentication: Required
Configuration resolution. If the version's task arguments contain ${config.KEY} references, they are resolved at run creation. Values are merged per key across up to three sources, highest priority first:
- Inline
configuration— ad-hoc key/value map in the request body (one-off tests / overrides; nothing persisted). - Inline
configurationId— references an existing configuration object, in the request body. - Stage-bound
configurationId— from the stage assignment (normal production path).
The merge is per key: an inline source can override a single key while the rest fall back to the stage-bound configuration. If any ${config.KEY} remains unresolved, the run is rejected with 422. Triggers (webhook/schedule) always use the stage-bound configuration only.
Request Body (optional):
// Inline ad-hoc data (highest priority)
{ "configuration": { "BUCKET": "test-bucket", "RAY_REPLICAS": "2" } }
// Reference a saved configuration
{ "configurationId": "exp-config" }
Omit the body entirely to use the stage-bound configuration.
Response:
{
"workflowId": "ml-training-pipeline",
"runId": "ml-training-pipeline-xyz123",
"stage": "test",
"versionId": "1.0.0",
"configurationId": "exp-config",
"resolvedConfiguration": { "BUCKET": "test-bucket", "RAY_REPLICAS": "2" },
"status": "Running",
"createdAt": "2025-06-18T14:00:00.000Z",
"startedAt": "2025-06-18T14:00:05.000Z",
"completedAt": null,
"message": "Progress 2/5",
"tasks": []
}
resolvedConfigurationis a snapshot of the exact values used to resolve references for this run, kept for audit even though configurations are mutable.configurationIdis the highest-priority persisted config that contributed (inline id, else stage-bound id, elsenullwhen only inline data was used).
Example Request:
curl -X POST "https://workflows.api.caip.bmw.cloud/v1/spaces/space_12345/workflows/ml-training-pipeline/runs?versionId=1.0.0&stage=test" \
-H "Authorization: Bearer <your-jwt-token>" \
-H "Content-Type: application/json" \
-d '{ "configuration": { "BUCKET": "test-bucket" } }'
Response Codes:
201: Run created404: Workflow or version not found422: A${config.KEY}reference could not be resolved
List Runs
- Supports pagination (
pageSize,pageToken) and filtering bystatus,versionId, andstage. - Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/runs - Authentication: Required
Response:
{
"runs": [
{
"workflowId": "ml-training-pipeline",
"runId": "ml-training-pipeline-xyz123",
"stage": "test",
"versionId": "1.0.0",
"status": "Running",
"createdAt": "2025-06-18T14:00:00.000Z",
"startedAt": "2025-06-18T14:00:05.000Z",
"completedAt": null,
"message": "Progress 2/5",
"tasks": []
}
],
"nextPageToken": "",
"totalSize": 1
}
Get a Run
- Returns a single run including live status and the resolved configuration snapshot.
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/runs/{runId} - Authentication: Required
Response Codes:
200: Run returned404: Run not found
Cancel a Run
- Requests cancellation of a running workflow. Idempotent.
- Endpoint:
POST /v1/spaces/{spaceId}/workflows/{workflowId}/runs/{runId}:cancel - Authentication: Required
Response Codes:
200: Cancellation requested (or run already terminal)
Retry a Run
- Retries failed tasks while preserving completed task results.
- Endpoint:
POST /v1/spaces/{spaceId}/workflows/{workflowId}/runs/{runId}:retry - Authentication: Required
- Accepts an optional
Idempotency-Keyheader.
Response Codes:
200: Retry triggered409: Run is not in aFailedstate
Triggers
A trigger binds a workflow to a stage and starts runs automatically — on an incoming webhook call or on a cron schedule. A trigger always runs the stage-bound version and configuration; it can never pass inline configuration. Each trigger has a triggerId that is unique within its workflow.
Create or Update a Trigger
- Idempotent upsert of a trigger. A newly created webhook trigger's response includes a one-time
webhookUrl; repeatedPUTs do not rotate the secret (use:rotate-secret). A schedule trigger requires the target stage to have a stage assignment with both a version and a configuration, otherwise it is not created and the API responds404; on success it schedules an Argo CronWorkflow, andenabled: falsesuspends it. - Endpoint:
PUT /v1/spaces/{spaceId}/workflows/{workflowId}/triggers/{triggerId} - Authentication: Required
Request Body (webhook):
{
"type": "webhook",
"stage": "prod",
"enabled": true,
"config": { "signatureValidation": true }
}
Request Body (schedule):
{
"type": "schedule",
"stage": "prod",
"enabled": true,
"config": { "schedule": "0 2 * * *", "timezone": "Europe/Berlin" }
}
config.schedule(5-field cron) is required for schedule triggers and omitted for webhook triggers.config.timezone(IANA name) defaults toUTC.config.signatureValidationdefaults tofalse.
Response (webhook create — includes the one-time webhookUrl):
{
"workflowId": "ml-training-pipeline",
"triggerId": "on-push",
"type": "webhook",
"stage": "prod",
"enabled": true,
"config": { "signatureValidation": true },
"webhookUrl": "https://workflows.api.caip.bmw.cloud/v1/webhooks/8f3c…",
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T14:00:00.000Z"
}
webhookUrlis returned only on create and rotate. It is shown exactly once — only its hash is stored and the plaintext is never recoverable.GET/list responses never include it.
Example Request:
curl -X PUT "https://workflows.api.caip.bmw.cloud/v1/spaces/space_12345/workflows/ml-training-pipeline/triggers/on-push" \
-H "Authorization: ******" \
-H "Content-Type: application/json" \
-d '{ "type": "webhook", "stage": "prod", "config": { "signatureValidation": true } }'
Response Codes:
201: Trigger created200: Existing trigger updated404: Target stage has no version + configuration assignment (schedule triggers)
Get a Trigger
- Returns a single trigger. The
webhookUrlis never included. - Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/triggers/{triggerId} - Authentication: Required
Response Codes:
200: Trigger returned404: Trigger not found
List Triggers
- Endpoint:
GET /v1/spaces/{spaceId}/workflows/{workflowId}/triggers - Authentication: Required
Response:
{
"items": [
{
"workflowId": "ml-training-pipeline",
"triggerId": "nightly-retrain",
"type": "schedule",
"stage": "prod",
"enabled": true,
"config": { "schedule": "0 2 * * *", "timezone": "Europe/Berlin" },
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T14:00:00.000Z"
}
],
"totalCount": 1
}
Invoke a Trigger
- Manually invokes a trigger for testing, starting a run from the trigger's stage version and configuration, tagged with the trigger id — the same path a schedule or webhook uses. Works for any trigger regardless of type or
enabledstate. - Endpoint:
POST /v1/spaces/{spaceId}/workflows/{workflowId}/triggers/{triggerId}:invoke - Authentication: Required
Example Request:
curl -X POST "https://workflows.api.caip.bmw.cloud/v1/spaces/space_12345/workflows/ml-training-pipeline/triggers/nightly-retrain:invoke" \
-H "Authorization: ******"
Response Codes:
201: Run created404: Trigger not found422: Target stage has no version assigned
Rotate a Webhook Secret
- Mints a fresh secret (invalidating the old URL) and returns a new one-time
webhookUrl. Webhook triggers only. - Endpoint:
POST /v1/spaces/{spaceId}/workflows/{workflowId}/triggers/{triggerId}:rotate-secret - Authentication: Required
Response:
{
"workflowId": "ml-training-pipeline",
"triggerId": "on-push",
"type": "webhook",
"stage": "prod",
"enabled": true,
"config": { "signatureValidation": true },
"webhookUrl": "https://workflows.api.caip.bmw.cloud/v1/webhooks/2a91…",
"createdAt": "2025-06-18T14:00:00.000Z",
"updatedAt": "2025-06-18T15:30:00.000Z"
}
Response Codes:
200: Secret rotated400: Trigger is not a webhook trigger404: Trigger not found
Delete a Trigger
- Deletes the trigger. For a schedule trigger this also removes its Argo CronWorkflow.
- Endpoint:
DELETE /v1/spaces/{spaceId}/workflows/{workflowId}/triggers/{triggerId} - Authentication: Required
Response Codes:
204: Trigger deleted
Webhooks
The webhook invocation endpoint is served under /v1 (not space-scoped). The secret in the path identifies the trigger — it is looked up by its SHA-256 hash.
Invoke a Webhook
- Fires a webhook trigger, starting a run for the trigger's stage assignment. When the trigger has
signatureValidationenabled, the secret is also the HMAC-SHA256 signing key: the caller must send anX-Signature-256header holding an HMAC computed over the raw request body. - Endpoint:
POST /v1/webhooks/{webhookSecret} - Authentication: The secret in the path; optionally an HMAC signature header.
Example Request (with signature validation):
curl -X POST "https://workflows.api.caip.bmw.cloud/v1/webhooks/8f3c…" \
-H "X-Signature-256: sha256=<hmac_hex>" \
-H "Content-Type: application/json" \
-d '{}'
Response Codes:
201: Run created401: Invalid or missing signature404: No enabled webhook trigger matches the secret409: Target stage has no version assigned
For the always-up-to-date, machine-readable contract, use the interactive Swagger UI exposed by a running instance at
/docs.