---
title: Create a workflow — POST /api/v1/workflows
description: Creates a cascade from a JSON definition (see CascadeDefinition for the node grammar), owned by the account behind the token.
canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/create-workflow"
doc_version: "1.0.0"
last_updated: "2026-10-09T18:04:24Z"
source: "public/api-docs/v1/swagger.yaml#createWorkflow"
---

# Create a workflow — POST /api/v1/workflows

`POST https://cascades.crscreditapi.com/api/v1/workflows`

- Operation id: `createWorkflow`
- Scopes: `workflows:write`
- Idempotent: no
- MCP tool: `cascades_create_workflow`
- OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `createWorkflow`)

Creates a cascade from a JSON `definition` (see `CascadeDefinition`
for the node grammar), owned by the account behind the token.
API-created cascades default to `trigger_type: manual` so they can
be run through `triggerWorkflow`.

`active` defaults to `true`. Accounts **without production access**
always get a draft (`active: false`) — silently, with a `note` in
the response explaining why. Drafts can still be triggered in test
mode; only live runs need an activated cascade and an approved
account. Requires `workflows:write`.

## Request body

`application/json` — `WorkflowCreateRequest`

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | string | yes |  |
| `definition` | CascadeDefinition | no | The node graph. Omit or pass `[]` to create an empty draft you fill in later. |
| `use_case` | string | no | Optional use case label. |
| `trigger_type` | string | no | API-created cascades default to `manual` so they can be triggered through this API. Unknown values fall back to `manual`. One of: `"manual"`, `"event"`, `"scheduled"`. |
| `active` | boolean | no | Whether the cascade is immediately triggerable in live mode. Omitted or `null` means `true`; pass `false` for a draft. Accounts without production access always receive a draft regardless of this value, and the response carries a `note` explaining why. |

```json
{
  "name": "Tenant Screening Standard",
  "use_case": "tenant_screening",
  "trigger_type": "manual",
  "definition": [
    {
      "id": "step_applicant",
      "type": "ApplicantSource",
      "name": "Applicant",
      "next_step": [
        "step_credit"
      ]
    },
    {
      "id": "step_credit",
      "type": "BillingProduct",
      "name": "Credit report",
      "billing_product_code": "EQF1003",
      "next_step": [
        "step_score_check"
      ]
    },
    {
      "id": "step_score_check",
      "type": "IfElse",
      "name": "Score at least 650?",
      "branches": {
        "pass": {
          "condition_groups": [
            {
              "conditions": [
                {
                  "source_node_id": "step_credit",
                  "field": "creditScore.fico9",
                  "operator": "greater_than_or_equal",
                  "value": 650
                }
              ]
            }
          ]
        }
      },
      "pass_steps": [
        "step_approve"
      ],
      "fail_steps": [
        "step_adverse_action"
      ]
    },
    {
      "id": "step_approve",
      "type": "Action",
      "name": "Approve",
      "action_type": "Approve"
    },
    {
      "id": "step_adverse_action",
      "type": "Action",
      "name": "Adverse action",
      "action_type": "AdverseAction",
      "adverse_action": {
        "recipient": "{{applicant.email}}",
        "action_taken": "denied",
        "send_strategy": "draft_first",
        "reason": "Credit score below the property minimum."
      }
    }
  ]
}
```

## Responses

### 201 — Created. `note` is present only when the account lacks production access and the cascade was saved as a draft.

`application/json`

| Field | Type | Required | Description |
|---|---|---|---|
| `data` | Workflow + SandboxDraftNote | no |  |
| &nbsp;&nbsp;`id` | integer | yes |  |
| &nbsp;&nbsp;`object` | string | yes |  |
| &nbsp;&nbsp;`code` | string, nullable | no | Short human-readable code assigned by Cascades. |
| &nbsp;&nbsp;`name` | string | yes |  |
| &nbsp;&nbsp;`active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. |
| &nbsp;&nbsp;`trigger_type` | string | yes |  One of: `"manual"`, `"event"`, `"scheduled"`. |
| &nbsp;&nbsp;`use_case` | string, nullable | no |  |
| &nbsp;&nbsp;`created_at` | string (date-time) | no |  |
| &nbsp;&nbsp;`updated_at` | string (date-time) | no |  |
| &nbsp;&nbsp;`note` | string | no | Present only when the account lacks production access and the cascade was therefore created as a draft. |
| `meta` | Meta | no |  |
| &nbsp;&nbsp;`request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. |

```json
{
  "data": {
    "id": 41,
    "object": "workflow",
    "code": "TSS-41",
    "name": "Tenant Screening Standard",
    "active": true,
    "trigger_type": "manual",
    "use_case": "tenant_screening",
    "created_at": "2026-09-09T14:00:00Z",
    "updated_at": "2026-09-09T14:00:00Z"
  },
  "meta": {
    "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f"
  }
}
```

### 400 — `malformed_body` (unparseable JSON) or `bad_request`

`application/json` — `ErrorEnvelope`

```json
{
  "error": {
    "code": "malformed_body",
    "message": "The request body could not be parsed. Send valid JSON matching the Content-Type header.",
    "details": [],
    "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f",
    "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/malformed_body"
  }
}
```

### 401 — Missing, malformed, expired or revoked bearer token. The `WWW-Authenticate` header carries the RFC 6750 challenge.

`application/json` — `ErrorEnvelope`

```json
{
  "error": {
    "code": "unauthorized",
    "message": "A valid bearer token is required.",
    "details": [],
    "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f",
    "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/unauthorized"
  }
}
```

### 403 — Token does not hold the required scope. `details` lists the scope(s) that were checked.

`application/json` — `ErrorEnvelope`

```json
{
  "error": {
    "code": "insufficient_scope",
    "message": "This token does not have the required scope.",
    "details": [
      {
        "field": "scope",
        "message": "executions:write"
      }
    ],
    "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f",
    "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/insufficient_scope"
  }
}
```

### 422 — `validation_failed` — e.g. blank `name`, a `definition` that is not an array of objects, or more than one `ApplicantSource` node.

`application/json` — `ErrorEnvelope`

```json
{
  "error": {
    "code": "validation_failed",
    "message": "The workflow could not be created.",
    "details": [
      {
        "field": "name",
        "message": "can't be blank"
      }
    ],
    "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f",
    "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed"
  }
}
```

### 429 — Rate limit exceeded (60 req/min per IP on `/api/v1`, 600 req/hour
per token prefix, or 300 req/5 min per IP globally). The body is the
standard error envelope and `Retry-After` gives the seconds until
the window resets.


`application/json` — `ErrorEnvelope`

```json
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry later.",
    "details": [],
    "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f",
    "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/rate_limited"
  }
}
```

## Code samples

### cURL

```shell
curl -X POST "https://cascades.crscreditapi.com/api/v1/workflows" \
  -H "Authorization: Bearer {{API_KEY}}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Tenant Screening Standard",
    "use_case": "tenant_screening",
    "trigger_type": "manual",
    "definition": [
      {
        "id": "step_applicant",
        "type": "ApplicantSource",
        "name": "Applicant",
        "next_step": [
          "step_credit"
        ]
      },
      {
        "id": "step_credit",
        "type": "BillingProduct",
        "name": "Credit report",
        "billing_product_code": "EQF1003",
        "next_step": [
          "step_score_check"
        ]
      },
      {
        "id": "step_score_check",
        "type": "IfElse",
        "name": "Score at least 650?",
        "branches": {
          "pass": {
            "condition_groups": [
              {
                "conditions": [
                  {
                    "source_node_id": "step_credit",
                    "field": "creditScore.fico9",
                    "operator": "greater_than_or_equal",
                    "value": 650
                  }
                ]
              }
            ]
          }
        },
        "pass_steps": [
          "step_approve"
        ],
        "fail_steps": [
          "step_adverse_action"
        ]
      },
      {
        "id": "step_approve",
        "type": "Action",
        "name": "Approve",
        "action_type": "Approve"
      },
      {
        "id": "step_adverse_action",
        "type": "Action",
        "name": "Adverse action",
        "action_type": "AdverseAction",
        "adverse_action": {
          "recipient": "{{applicant.email}}",
          "action_taken": "denied",
          "send_strategy": "draft_first",
          "reason": "Credit score below the property minimum."
        }
      }
    ]
  }'
```

### Node.js

```javascript
const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows", {
  method: "POST",
  headers: {
    "Authorization": "Bearer {{API_KEY}}",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "name": "Tenant Screening Standard",
    "use_case": "tenant_screening",
    "trigger_type": "manual",
    "definition": [
      {
        "id": "step_applicant",
        "type": "ApplicantSource",
        "name": "Applicant",
        "next_step": [
          "step_credit"
        ]
      },
      {
        "id": "step_credit",
        "type": "BillingProduct",
        "name": "Credit report",
        "billing_product_code": "EQF1003",
        "next_step": [
          "step_score_check"
        ]
      },
      {
        "id": "step_score_check",
        "type": "IfElse",
        "name": "Score at least 650?",
        "branches": {
          "pass": {
            "condition_groups": [
              {
                "conditions": [
                  {
                    "source_node_id": "step_credit",
                    "field": "creditScore.fico9",
                    "operator": "greater_than_or_equal",
                    "value": 650
                  }
                ]
              }
            ]
          }
        },
        "pass_steps": [
          "step_approve"
        ],
        "fail_steps": [
          "step_adverse_action"
        ]
      },
      {
        "id": "step_approve",
        "type": "Action",
        "name": "Approve",
        "action_type": "Approve"
      },
      {
        "id": "step_adverse_action",
        "type": "Action",
        "name": "Adverse action",
        "action_type": "AdverseAction",
        "adverse_action": {
          "recipient": "{{applicant.email}}",
          "action_taken": "denied",
          "send_strategy": "draft_first",
          "reason": "Credit score below the property minimum."
        }
      }
    ]
  })
});

const { data, meta } = await response.json();
console.log(response.status, data, meta.request_id);
```

### Python

```python
import requests

response = requests.post(
    "https://cascades.crscreditapi.com/api/v1/workflows",
    headers={
        "Authorization": "Bearer {{API_KEY}}",
    },
    json={
      "name": "Tenant Screening Standard",
      "use_case": "tenant_screening",
      "trigger_type": "manual",
      "definition": [
        {
          "id": "step_applicant",
          "type": "ApplicantSource",
          "name": "Applicant",
          "next_step": [
            "step_credit"
          ]
        },
        {
          "id": "step_credit",
          "type": "BillingProduct",
          "name": "Credit report",
          "billing_product_code": "EQF1003",
          "next_step": [
            "step_score_check"
          ]
        },
        {
          "id": "step_score_check",
          "type": "IfElse",
          "name": "Score at least 650?",
          "branches": {
            "pass": {
              "condition_groups": [
                {
                  "conditions": [
                    {
                      "source_node_id": "step_credit",
                      "field": "creditScore.fico9",
                      "operator": "greater_than_or_equal",
                      "value": 650
                    }
                  ]
                }
              ]
            }
          },
          "pass_steps": [
            "step_approve"
          ],
          "fail_steps": [
            "step_adverse_action"
          ]
        },
        {
          "id": "step_approve",
          "type": "Action",
          "name": "Approve",
          "action_type": "Approve"
        },
        {
          "id": "step_adverse_action",
          "type": "Action",
          "name": "Adverse action",
          "action_type": "AdverseAction",
          "adverse_action": {
            "recipient": "{{applicant.email}}",
            "action_taken": "denied",
            "send_strategy": "draft_first",
            "reason": "Credit score below the property minimum."
          }
        }
      ]
    },
)
response.raise_for_status()
payload = response.json()
print(payload["data"], payload["meta"]["request_id"])
```

### Ruby

```ruby
require "net/http"
require "json"

uri = URI("https://cascades.crscreditapi.com/api/v1/workflows")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer {{API_KEY}}"
request["Content-Type"] = "application/json"
request.body = <<~JSON
  {
    "name": "Tenant Screening Standard",
    "use_case": "tenant_screening",
    "trigger_type": "manual",
    "definition": [
      {
        "id": "step_applicant",
        "type": "ApplicantSource",
        "name": "Applicant",
        "next_step": [
          "step_credit"
        ]
      },
      {
        "id": "step_credit",
        "type": "BillingProduct",
        "name": "Credit report",
        "billing_product_code": "EQF1003",
        "next_step": [
          "step_score_check"
        ]
      },
      {
        "id": "step_score_check",
        "type": "IfElse",
        "name": "Score at least 650?",
        "branches": {
          "pass": {
            "condition_groups": [
              {
                "conditions": [
                  {
                    "source_node_id": "step_credit",
                    "field": "creditScore.fico9",
                    "operator": "greater_than_or_equal",
                    "value": 650
                  }
                ]
              }
            ]
          }
        },
        "pass_steps": [
          "step_approve"
        ],
        "fail_steps": [
          "step_adverse_action"
        ]
      },
      {
        "id": "step_approve",
        "type": "Action",
        "name": "Approve",
        "action_type": "Approve"
      },
      {
        "id": "step_adverse_action",
        "type": "Action",
        "name": "Adverse action",
        "action_type": "AdverseAction",
        "adverse_action": {
          "recipient": "{{applicant.email}}",
          "action_taken": "denied",
          "send_strategy": "draft_first",
          "reason": "Credit score below the property minimum."
        }
      }
    ]
  }
JSON

response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
  http.request(request)
end
payload = JSON.parse(response.body)
puts payload["data"], payload.dig("meta", "request_id")
```

### Go

```go
package main

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
)

func main() {
	body := []byte(`{
	  "name": "Tenant Screening Standard",
	  "use_case": "tenant_screening",
	  "trigger_type": "manual",
	  "definition": [
	    {
	      "id": "step_applicant",
	      "type": "ApplicantSource",
	      "name": "Applicant",
	      "next_step": [
	        "step_credit"
	      ]
	    },
	    {
	      "id": "step_credit",
	      "type": "BillingProduct",
	      "name": "Credit report",
	      "billing_product_code": "EQF1003",
	      "next_step": [
	        "step_score_check"
	      ]
	    },
	    {
	      "id": "step_score_check",
	      "type": "IfElse",
	      "name": "Score at least 650?",
	      "branches": {
	        "pass": {
	          "condition_groups": [
	            {
	              "conditions": [
	                {
	                  "source_node_id": "step_credit",
	                  "field": "creditScore.fico9",
	                  "operator": "greater_than_or_equal",
	                  "value": 650
	                }
	              ]
	            }
	          ]
	        }
	      },
	      "pass_steps": [
	        "step_approve"
	      ],
	      "fail_steps": [
	        "step_adverse_action"
	      ]
	    },
	    {
	      "id": "step_approve",
	      "type": "Action",
	      "name": "Approve",
	      "action_type": "Approve"
	    },
	    {
	      "id": "step_adverse_action",
	      "type": "Action",
	      "name": "Adverse action",
	      "action_type": "AdverseAction",
	      "adverse_action": {
	        "recipient": "{{applicant.email}}",
	        "action_taken": "denied",
	        "send_strategy": "draft_first",
	        "reason": "Credit score below the property minimum."
	      }
	    }
	  ]
	}`)
	req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/workflows", bytes.NewBuffer(body))
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer {{API_KEY}}")
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	out, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.StatusCode, string(out))
}
```

### Agent

```shell
claude mcp add cascades \
  -e CASCADES_API_KEY={{API_KEY}} \
  -e CASCADES_PROD_BASE_URL=https://cascades.crscreditapi.com \
  -- npx -y @crscreditapi/cascades-mcp-server

# Then ask your assistant:
#   "Create a workflow and summarise the result."
# It will call the `cascades_create_workflow` tool with your key.
```

## Related

- [Cascades](https://cascades.crscreditapi.com/docs/concepts/cascades)
- [Generate a cascade with AI](https://cascades.crscreditapi.com/docs/guides/ai-generate-a-cascade)

## Sitemap

See the full [sitemap](https://cascades.crscreditapi.com/sitemap.md) for all pages.
