--- title: Cascades API description: Run tenant, employment and credit screening decisions through one REST API and MCP server. Start in the sandbox, go live when approved. canonical_url: "https://cascades.crscreditapi.com/docs" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/index.md --- # Cascades API Cascades runs **decision workflows** ("cascades") against an applicant: pull the reports the cascade asks for — credit, criminal, eviction, identity — evaluate the operator's rules, and land on a decision. The API lets your systems trigger those runs, read the outcome, pause for a human reviewer, and receive signed lifecycle events. The MCP server exposes the same surface to coding agents and assistants. ## What can I do with the API? | You want to | Use | |---|---| | Start a screening for one applicant | [`POST /workflows/{id}/trigger`](/docs/api/workflows/trigger-workflow) | | Read the decision and per-step outputs | [`GET /executions/{id}`](/docs/api/executions/get-execution) | | Screen hundreds of applicants at once | [`POST /workflows/{id}/batches`](/docs/api/batches/create-batch) | | Let a person approve or deny a paused run | [`POST /executions/{id}/review_decisions`](/docs/api/executions/submit-review-decision) | | Be told when a run finishes | [Webhooks](/docs/concepts/webhooks) | | Build or edit a cascade from code or an agent | [`POST /workflows`](/docs/api/workflows/create-workflow), [`POST /workflows/generate`](/docs/api/workflows/generate-workflow) | | Download the report and adverse-action PDFs | [Artifacts](/docs/api/executions/list-report-artifacts) | ## How do I integrate, in order? 1. **Mint a sandbox key** at [/api-keys](/api-keys). It carries every scope and expires in 90 days. See [Keys, scopes and rate limits](/docs/concepts/scopes-and-keys). 2. **Subscribe a webhook** with [`POST /webhook_subscriptions`](/docs/api/webhooks/create-webhook-subscription) and store the `signing_secret` — it is shown once. 3. **Trigger a run** with a unique `Idempotency-Key` and your own `external_reference` so you can correlate results. 4. **Poll or wait**: `trigger` answers `202 Accepted`. Poll `GET /executions/{id}` until `status` is terminal, or wait for `execution.completed`. 5. **Handle human review** if the cascade has a review node: the run pauses at `awaiting_review` until someone decides. 6. **Go live** once your account is approved: switch `execution_mode` to `live`. Until then every run uses synthetic data and never bills. ```bash curl -X POST "https://cascades.crscreditapi.com/api/v1/workflows/27/trigger" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: rv-app-20491" \ -d '{ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee" } }, "execution_mode": "test" }' ``` The full walkthrough, with a Node, Python, Ruby and MCP version of every step, is the [quickstart](/docs/quickstart). ## What does a response look like? Every success is `{ "data": …, "meta": { "request_id": … } }`; collections add `page`, `per_page`, `total_count`, `total_pages` and `object` to `meta`. Every error is `{ "error": { "code", "message", "details", "request_id", "doc_url" } }`, and `doc_url` links to the page for that code under [Errors](/docs/api/errors). Keep `request_id` in your logs — support correlates on it. ## Where is the reference? - [API reference](/docs/api) — every endpoint, generated from the [OpenAPI spec](/openapi.json). - [Webhook events](/docs/api/webhooks) — the catalog, signing scheme and retry schedule. - [MCP server](/docs/mcp) — install the `@crscreditapi/cascades-mcp-server` package into Claude Code, Cursor or VS Code. - [Changelog](/docs/changelog) — what changed, newest first. ## Docs for agents Everything on this site is available in a form a coding agent or an LLM can read without scraping HTML: - [/llms.txt](/llms.txt) — a short index of every page with a one-line description, the place to point an agent first. - [/llms-full.txt](/llms-full.txt) — the whole site as one markdown document, for a single-fetch context load. - [/openapi.json](/openapi.json) (also [/openapi.yaml](/openapi.yaml)) — the OpenAPI 3.0 spec the reference is generated from, with `operationId`, `x-scopes`, `x-async` and `x-webhooks`. - **Markdown twins** — append `.md` to any docs URL (`/docs/concepts/executions.md`) or send `Accept: text/markdown` to get the page as clean markdown with front matter. - [/docs/mcp](/docs/mcp) — the MCP server that wraps the API as tools for Claude Code, Cursor, VS Code and other clients, with per-client install pages. --- --- title: Quickstart description: Mint a sandbox key, trigger your first cascade and read the decision in under five minutes — with curl, Node, Python, Ruby or an MCP agent. canonical_url: "https://cascades.crscreditapi.com/docs/quickstart" doc_version: "1.0.0" last_updated: "2026-10-08T16:52:14Z" source: docs/quickstart.md.erb --- # Quickstart Five minutes from zero to a completed screening run. Everything below runs in **test mode**: synthetic report data, no bureau calls, no charges — and it works on draft cascades, so you do not need an activated account. > **Signed in?** > > When you are signed in, every `{{API_KEY}}` on this site is replaced with a sandbox key minted for you, and the **Try it** panel on each endpoint runs the request against your own workspace. ### Mint a sandbox key Open [/api-keys](/api-keys) and click **Generate key** (it reads **Regenerate key** when you already have one, and regenerating revokes the old key). The raw token is shown exactly once — copy it somewhere safe. Sandbox keys carry every scope (`workflows:read`, `workflows:write`, `executions:read`, `executions:read:full`, `executions:write`, `workflow_templates:read`, `webhooks:read`, `webhooks:write`) and expire after 90 days. Send it as a bearer token on every request: ```http Authorization: Bearer {{API_KEY}} ``` ### Find a cascade to run List the cascades in your workspace. If you have none yet, build one in the visual builder at [/cascades](/cascades), copy a [template](/docs/api/workflow-templates/list-workflow-templates), or [generate one from a sentence](/docs/guides/ai-generate-a-cascade). **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/workflows" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflows", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflows", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List workflows and summarise the result." # It will call the `cascades_list_workflows` tool with your key. ``` Note the `id` of the cascade you want — the examples below use `27`. ### Trigger a run `POST /workflows/{id}/trigger` starts an execution. Three things matter: - **`Idempotency-Key` is required.** Use something unique to the business event (an application id). Re-sending the same key and body returns the original execution instead of starting a second one. - **`execution_mode` defaults to `test`.** Leave it there until your account is approved for live runs. - **`external_reference`** is your own id; it comes back on the execution and on every webhook. **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/workflows/27/trigger" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -d '{ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27/trigger", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, body: JSON.stringify({ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }) }); 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/27/trigger", headers={ "Authorization": "Bearer {{API_KEY}}", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6", }, json={ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }, ) 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/27/trigger") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request["Idempotency-Key"] = "3fa85f64-5717-4562-b3fc-2c963f66afa6" request.body = <<~JSON { "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" } 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(`{ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/workflows/27/trigger", bytes.NewBuffer(body)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") req.Header.Set("Content-Type", "application/json") req.Header.Set("Idempotency-Key", "3fa85f64-5717-4562-b3fc-2c963f66afa6") 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: # "Trigger a workflow 27 and summarise the result." # It will call the `cascades_trigger_workflow` tool with your key. ``` The response is `202 Accepted` with the new execution — `status` will be `pending`, not the final answer. > Try it in the browser: https://cascades.crscreditapi.com/docs/api/workflows/trigger-workflow — a signed-in sandbox key runs `POST /workflows/{id}/trigger` against the test identities. ### Poll for the decision Cascades are asynchronous. Poll `GET /executions/{id}` every second or two until `status` leaves `pending` / `running` / `waiting` (`queued` only appears on batch child rows — see [Batches](/docs/concepts/batches)). Terminal statuses are `completed`, `failed`, `cancelled` and `skipped`; `awaiting_review` means a person has to decide (see [Handle human review](/docs/guides/handle-human-review)). **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/executions/1842" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show an execution 1842 and summarise the result." # It will call the `cascades_get_execution` tool with your key. ``` With `executions:read:full` the payload also carries `step_outputs` — the per-node results, including the report data the decision was made on — and `failed_steps`. ### Stop polling: subscribe to webhooks Polling is fine for a first run; in production subscribe once and let Cascades call you. `POST /webhook_subscriptions` returns a `signing_secret` exactly once. Every delivery is signed; verify it before trusting the payload. **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }) }); 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/webhook_subscriptions", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }, ) 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/webhook_subscriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] } 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(`{ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions", 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 webhook subscription using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /webhook_subscriptions with curl. ``` See [Webhooks](/docs/concepts/webhooks) for the event catalog and [Verify webhook signatures](/docs/guides/verify-webhooks) for drop-in verifiers. ## Prefer to let an agent do it? Install the MCP server and ask your assistant in plain English. The server wraps the same endpoints with the same key and scopes. ```bash 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: *"List my cascades, run cascade 27 for a registered test subject in test mode, and explain the decision."* Details and per-client install pages are under [MCP server](/docs/mcp). ## What next? - [Your first screening](/docs/guides/first-screening) — the full flow with a test identity, a chosen mock outcome, human review and adverse action. - [Sandbox vs live](/docs/concepts/sandbox-vs-live) — what changes when you flip `execution_mode`. - [API reference](/docs/api) — every endpoint with request and response schemas. --- --- title: Sandbox vs live description: What test mode does, what live mode requires, and exactly which requests are refused until your account is approved for production. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/sandbox-vs-live" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: docs/concepts/sandbox-vs-live.md --- # Sandbox vs live Every run has an `execution_mode`: `test` or `live`. The mode is chosen per request, not per key, so the same integration moves from sandbox to production by changing one field. ## What does test mode do? - **Synthetic data.** Nothing calls a billable bureau or vendor and nothing is billed. For most applicants every product node returns its schema mock: `happy_path` (clean file, no records) by default. - **Pick the outcome with `scenario`.** Add `"scenario": "fail_path"` to `payload` to make every product node return its decline mock (for example the eviction node reports two filings and `recommendation: FAIL`), so `fail_steps` and adverse-action branches run. A node's `mock_scenario` config overrides it. - **Registered test subjects replay.** When a trigger's applicant is a [registered test subject](/docs/sandbox/test-identities) and that subject is registered for every product node in the cascade, the run replays the subject's authored bureau responses instead of the mocks, still at no charge. The execution's `input_context.sandbox_replay` is `true`, and `scenario` has no effect on that run. Any other applicant, and every batch row, gets the mocks. - **Drafts run.** A cascade with `active: false` accepts a single `execution_mode: test` trigger, so you can exercise it before activating. Batches and re-evaluations still need an active cascade (`409 workflow_inactive`), so a sandbox account cannot run batches. See [Batches](/docs/concepts/batches). - **Human review resumes inline.** `POST /executions/{id}/review_decisions` on a test-mode execution runs the chosen branch synchronously and answers `200` with the final state; live-mode decisions enqueue the resume and answer `202`. - **Webhooks still fire.** `execution.*` and `adverse_action.*` events are delivered for test runs too, so you can build your handler end to end. `execution.*` payloads carry `"execution_mode": "test"`; `adverse_action.*` payloads do not carry it — join on `workflow_execution_id` and read `input_context.execution_mode` from `GET /executions/{id}`. ## What does live mode require? Two things, checked on every live request: 1. **An approved account.** Approval is granted by CRS after the activation form at [/activate](/activate) is reviewed. Until then every `execution_mode: live` request is refused with `403 live_mode_forbidden`. 2. **An active cascade.** Live triggers on a draft return `409 workflow_inactive`. Live runs pull real consumer reports and are billable. On the CRS **sandbox** host, a live-mode run from an approved account with a normal key (not a docs key) also matches the registered test subjects: when the applicant matches a registry entry, the authored bureau response is replayed instead of a vendor pull, and a miss reaches the billable vendor — see [Test identities](/docs/sandbox/test-identities). You need a permissible purpose under the FCRA for every live pull — see [FCRA permissible purpose](/docs/compliance/fcra-permissible-purpose). ## Which requests are gated? | Request | Sandbox account (not approved) | |---|---| | `POST /workflows/{id}/trigger` with `execution_mode: live` | `403 live_mode_forbidden` | | `POST /workflows/{id}/batches`, test or live | `409 workflow_inactive`: the account's cascades are drafts, and a draft runs no batch | | `POST /batches/{id}/resume`, `retry_failed` on a live batch | `403 live_mode_forbidden` | | `POST /executions/{id}/re_evaluations` of a live execution | `403 live_mode_forbidden` | | `POST /executions/{id}/review_decisions` on a live execution | `403 live_mode_forbidden` | | `POST /workflows` with `active: true` | Created as a **draft**; the response carries a `note` explaining why | | `PATCH /workflows/{id}` with `active: true` | `403 activation_forbidden` | Everything else (reading, single test-mode triggers, webhooks, templates, deleting) works the same in both modes. ## How do I tell which mode a run used? - The `trigger` and `batch` responses echo `execution_mode`. - `GET /executions/{id}` includes it in `input_context.execution_mode`. - Webhook payloads for `execution.*` events carry `execution_mode`; `adverse_action.*` payloads do not, so resolve the execution first. Filter your own records on it before acting on a decision: a test-mode approval is a synthetic approval. ## Keys minted from the docs When you are signed in, the key injected into these pages is a short-lived, **sandbox-only** token: it expires after an hour or when you sign out, and is refused for any live request even on an approved account. On an approved account the refusal says so: live requests answer `403 live_mode_forbidden` with "Docs keys are sandbox-only, so they never run live", activation answers `403 activation_forbidden`, and `POST /workflows` creates a draft with a note that names the docs key. Mint a real key at [/api-keys](/api-keys) for your integration, or activate the cascade in the app. ## Going live checklist - [ ] Account approved (the sandbox banner in the app disappears) - [ ] Cascade active (`active: true`), tested end to end in test mode - [ ] Integration key holds only the scopes you need - [ ] `Idempotency-Key` derived from a stable business id - [ ] Webhook signature verification rejects stale `t` and wrong `v1` - [ ] Your side records `execution_mode` from the trigger response or `GET /executions/{id}` and never treats a test decision as real --- --- title: Test identities description: The synthetic applicants registered in the CRS sandbox, what test mode really does with them, and how to pick a mock outcome for a test run. canonical_url: "https://cascades.crscreditapi.com/docs/sandbox/test-identities" doc_version: "1.0.0" last_updated: "2026-10-09T14:09:36Z" source: docs/sandbox/test-identities.md.erb --- # Test identities Cascades keeps a set of **registered test subjects** in the CRS sandbox test-case registry — synthetic applicants authored with a specific history (a borderline credit file, a misdemeanor, three eviction filings). What they do depends on the mode of the run: - In **test mode** a trigger (`POST /workflows/{id}/trigger`) whose applicant is a registered subject replays that subject's authored bureau responses, at no charge, when the subject is registered for every product node in the cascade. The execution's `input_context.sandbox_replay` is then `true`. Any other applicant, a subject that covers only some of the cascade's products, and every batch row get the schema mock for each product node: `happy_path` (clean file, no records) by default, or the decline mock when you add `"scenario": "fail_path"` to `payload`. - On a **live-mode sandbox run** (approved account, key minted at [/api-keys](/api-keys), CRS sandbox host) the registry matches the applicant's name and SSN and replays the authored bureau response for that subject instead of billing a vendor, so the decision path is the one a live applicant with that history would take. > **Sandbox only** > > These identities exist only in the sandbox registry. The SSNs are in the `666-xx-xxxx` range reserved for testing and belong to nobody. Keys minted from these docs cannot run live at all (`403 live_mode_forbidden`). With a normal key, a live pull that misses the registry — a typo in the name, a product the subject was not registered for — falls through to a real, billable vendor call and returns nothing useful. ## How do I get the subjects for my cascade? Ask the API. `GET /workflows/{id}/test_cases` walks the cascade's product nodes, maps each product to its registry family, and returns every Cascades case registered for it (`case_name` starts with `Cascades_`) with a `payload` you can send straight to `trigger`: **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/27/test_cases") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List sandbox test cases for a workflow 27 and summarise the result." # It will call the `cascades_list_test_cases` tool with your key. ``` ```json { "data": { "object": "test_case_list", "workflow_id": 27, "test_cases": [ { "id": 73, "case_name": "Cascades_XProduct_Approve_EFX", "product_code": "EQF1003", "payload": { "applicant": { "first_name": "AVERY", "last_name": "CLARKSON", "middle_name": "", "name": "AVERY CLARKSON", "email": "avery.clarkson@example.com", "phone": "", "ssn": "…", "dob": "1982-01-01" }, "property": { "address": "1803 Norma", "unit": "", "city": "Cottonwood", "state": "CA", "zip": "91502", "full_address": "1803 Norma, Cottonwood, CA 91502" } } } ] }, "meta": { "request_id": "…" } } ``` A credit case's own registry entry has only the name and SSN. The `payload` fills the date of birth and address from the same subject's other cases (here Avery's criminal case), as the table below does, because CRS refuses a credit order without them. An unreachable registry returns an empty list, never an error, so discovery degrades to "no test cases" rather than failing your integration. ## Choosing the outcome in test mode For an applicant that is not a registered subject, test mode returns mocks, and `scenario` picks which ones. To walk a decline or adverse-action branch, add `scenario` to the trigger `payload`: ```json { "payload": { "scenario": "fail_path", "applicant": { "first_name": "Raymond", "last_name": "Okafor" } }, "execution_mode": "test" } ``` `happy_path` (the default) returns a clean file from every product node; `fail_path` returns each node's decline mock (for example the eviction node reports two filings and `recommendation: FAIL`). A node whose config sets `mock_scenario` overrides the payload value. `scenario` is ignored in live mode, and on a test run that replays a registered subject: that run returns the subject's authored outcome. ## Registered subjects These are the Cascades cases in the registry right now, read live from it; `GET /workflows/{id}/test_cases` returns the registered subjects for your cascade's products. Each one replays its authored bureau response on a test-mode trigger (when it covers every product node in the cascade) and on a live sandbox run. Identity and watchlist products have no registry coverage today, so a cascade with one of them always gets mocks in test mode. Product codes map to registry families by prefix: `TUC*` → TransUnion credit, `EQF*` → Equifax credit, `EXP1*` → Experian credit, `CIC4002` → criminal, `CIC4003` → eviction. A subject only replays for the family it was registered under — a live pull of a credit subject through a criminal node misses the registry and reaches the vendor. **Scores on the Equifax test files.** Each registry Equifax credit file (`EQF*`) carries a FICO Score 9 (Equifax model `05206`), a FICO Score 5 (`02958`) and a VantageScore 4.0. `creditScore.fico9` reads only the FICO 9. Avery Clarkson's FICO 9 is 700, so the `createWorkflow` example (`creditScore.fico9 >= 650`) approves her; Blake Moreno and Carmen Delacruz are at 619 and take the fail branch. > **Synthetic identities, sandbox only** > > Synthetic test subjects registered in the CRS sandbox, not real people. The `666-xx-xxxx` SSNs belong to nobody. A test-mode trigger for a subject registered for every product node in the cascade replays its authored bureau responses at no charge (`input_context.sandbox_replay` is `true`); other test runs return schema mocks (use `payload.scenario`). Live sandbox runs replay them too, but docs keys cannot run live. | Name | Registry case | Family | SSN | DOB | Property address | |---|---|---|---|---|---| | Sandrx Bowkey | `Cascades_Credit_660_Inq0` | Credit (TransUnion) | `666221524` | — | 1268 BLOSSOM PT RD, WELCOME, MD, 20693 | | Selma Bowkey | `Cascades_Credit_660_InqMany` | Credit (TransUnion) | `666221524` | — | 1268 BLOSSOM PT RD, WELCOME, MD, 20693 | | Teron Bowkey | `Cascades_Credit_700_Inq0` | Credit (TransUnion) | `666221524` | — | 1268 BLOSSOM PT RD, WELCOME, MD, 20693 | | Tilda Bowkey | `Cascades_Credit_700_InqMany` | Credit (TransUnion) | `666221524` | — | 1268 BLOSSOM PT RD, WELCOME, MD, 20693 | | Umber Bowkey | `Cascades_Credit_781_Inq0` | Credit (TransUnion) | `666221524` | — | 1268 BLOSSOM PT RD, WELCOME, MD, 20693 | | Usher Bowkey | `Cascades_Credit_781_InqMany` | Credit (TransUnion) | `666221524` | — | 1268 BLOSSOM PT RD, WELCOME, MD, 20693 | | Avery Clarkson | `Cascades_XProduct_Approve_Credit` | Credit (TransUnion) | `666900001` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Blake Moreno | `Cascades_XProduct_CriminalFail_Credit` | Credit (TransUnion) | `666900002` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Carmen Delacruz | `Cascades_XProduct_EvictionFail_Credit` | Credit (TransUnion) | `666900003` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Avery Clarkson | `Cascades_XProduct_Approve_EFX` | Credit (Equifax) | `666900001` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Blake Moreno | `Cascades_XProduct_CriminalFail_EFX` | Credit (Equifax) | `666900002` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Carmen Delacruz | `Cascades_XProduct_EvictionFail_EFX` | Credit (Equifax) | `666900003` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Avery Clarkson | `Cascades_XProduct_Approve_EXP` | Credit (Experian) | `666900001` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Blake Moreno | `Cascades_XProduct_CriminalFail_EXP` | Credit (Experian) | `666900002` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Carmen Delacruz | `Cascades_XProduct_EvictionFail_EXP` | Credit (Experian) | `666900003` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | June Abara | `Cascades_Criminal_California` | Criminal | `666-51-2206` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Theo Lindgren | `Cascades_Criminal_Clean` | Criminal | `666-51-2204` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Omar Haddad | `Cascades_Criminal_Misdemeanor` | Criminal | `666-51-2205` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Avery Clarkson | `Cascades_XProduct_Approve_Criminal` | Criminal | `666-90-0001` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Blake Moreno | `Cascades_XProduct_CriminalFail_Criminal` | Criminal | `666-90-0002` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Carmen Delacruz | `Cascades_XProduct_EvictionFail_Criminal` | Criminal | `666-90-0003` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Delia Marquez | `Cascades_Eviction_California` | Eviction | `666-51-2203` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Priya Narayan | `Cascades_Eviction_Clean` | Eviction | `666-51-2202` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Raymond Okafor | `Cascades_Eviction_Multi` | Eviction | `666-51-2201` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Avery Clarkson | `Cascades_XProduct_Approve_Eviction` | Eviction | `666-90-0001` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Blake Moreno | `Cascades_XProduct_CriminalFail_Eviction` | Eviction | `666-90-0002` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | | Carmen Delacruz | `Cascades_XProduct_EvictionFail_Eviction` | Eviction | `666-90-0003` | 1982-01-01 | 1803 Norma, Cottonwood, CA, 91502 | Discover the subjects registered for *your* cascade with `GET https://cascades.crscreditapi.com/api/v1/workflows/{id}/test_cases`; each `payload` is ready to POST to `/workflows/{id}/trigger`. ## Payload shape that matches the registry The registry matches on the applicant's **name and SSN** (names are upper-cased and the SSN digit-stripped before comparison), and the credit provider reads the date of birth from `applicant.dob` and the address from a separate `property` object — not from `applicant.address`. Send the `payload` from `test_cases` unchanged and you cannot miss. ## Related - [Sandbox vs live](/docs/concepts/sandbox-vs-live) - [Your first screening](/docs/guides/first-screening) - [`GET /workflows/{id}/test_cases`](/docs/api/workflows/list-workflow-test-cases) --- --- title: Cascades description: What a cascade is, the node grammar behind the definition array, and how to list, create, update, activate and delete cascades through the API. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/cascades" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/concepts/cascades.md --- # Cascades A **cascade** is a decision workflow: which reports to pull, in what order, which rules to evaluate, and what happens on each outcome — approve, send to a reviewer, decline with an adverse-action notice. Operators usually author cascades in the visual builder; the API exposes the same objects (the resource is named `workflow`) so code and agents can list, run, create and edit them. ## What does a cascade look like over the API? ```json { "id": 27, "object": "workflow", "name": "Tenant Screening Standard", "active": true, "trigger_type": "manual", "use_case": "tenant_screening", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ``` `GET /workflows/{id}` adds `definition`: the node graph as a JSON array. `active: false` marks a **draft** — drafts accept a single test-mode trigger but cannot run live, in batches, or be re-evaluated. ## What is in the definition? `definition` is an array of nodes. Each node has an `id`, a `type`, and the edge keys that wire it to the next nodes. The vocabulary is fixed (it is the same grammar the AI generator and the validator use): | Node type | Purpose | Required keys | |---|---|---| | `ApplicantSource` | Entry point; receives `payload.applicant` | — | | `BillingProduct` | Pulls one report (credit, criminal, eviction, identity…) | `billing_product_code` | | `IfElse` | Evaluates conditions and branches | `branches` | | `Wait` | Pauses for a duration or until a date | `wait` | | `GoTo` | Jumps to another node at runtime | `target_step` | | `ShowResults` | Terminal node that surfaces the outcome | — | | `Action` | Side effect chosen by `action_type` | `action_type` | `Product` and `Condition` are accepted as legacy aliases for `BillingProduct` and `IfElse`. **Action subtypes** (`action_type`): `Approve`, `SendEmail`, `AdverseAction`, `InternalNotification`. **Edges.** `next_step` is the default forward edge. An `IfElse` fans out through `pass_steps`, `human_review_steps` and `fail_steps` — each an array of node ids. That placement is what gives downstream nodes their meaning: an `AdverseAction` under `fail_steps` auto-handles a decline; under `human_review_steps` it prepares a draft at flag time and waits for a reviewer to send it. See [Adverse action](/docs/concepts/adverse-action). **Condition operators** inside `branches`: `equals`, `not_equals`, `greater_than`, `greater_than_or_equal`, `less_than`, `less_than_or_equal`, `contains`, `not_contains`, `starts_with`, `ends_with`, `is_empty`, `is_not_empty`, `is_true`, `is_false`. Fields are dotted paths into a product's output, e.g. `creditScore.fico9` or `searchSummary.recordsFound`. A minimal, runnable definition: ```json [ { "id": "start", "type": "ApplicantSource", "name": "Applicant", "next_step": ["credit"] }, { "id": "credit", "type": "BillingProduct", "name": "Credit report", "billing_product_code": "TUC1001", "next_step": ["decide"] }, { "id": "decide", "type": "IfElse", "name": "FICO gate", "branches": [{ "conditions": [{ "field": "creditScore.fico9", "operator": "greater_than_or_equal", "value": 680 }] }], "pass_steps": ["approve"], "human_review_steps": [], "fail_steps": ["done"] }, { "id": "approve", "type": "Action", "action_type": "Approve", "name": "Approve", "next_step": ["done"] }, { "id": "done", "type": "ShowResults", "name": "Results", "next_step": [] } ] ``` The full schema is [`CascadeDefinition`](/docs/api/schemas/CascadeDefinition) in the reference. ## How do I list and read cascades? `GET /workflows` returns the cascades your key's user owns, newest first, paginated. Filter with `filter[active]=true`, `filter[use_case]=tenant_screening` or `filter[trigger_type]=manual`. `GET /workflows/{id}` returns one with its `definition`. Both need `workflows:read`. Cascades belong to the **user** behind your key, so two API clients of the same account see the same cascades; executions, by contrast, are isolated per client. See [Keys, scopes and rate limits](/docs/concepts/scopes-and-keys). ## How do I create or change one? - `POST /workflows` with `name` and `definition` (and optionally `active`, `trigger_type`, `use_case`) creates a cascade. Needs `workflows:write`. The definition is validated against the grammar and against the products your account can use; failures come back as `422 validation_failed` with one `details` entry per problem. - `PATCH /workflows/{id}` changes only the fields you send. To edit the graph, read it, modify the array, and send the whole `definition` back. - `DELETE /workflows/{id}` soft-deletes: the cascade disappears from lists and can no longer be triggered; its past executions remain readable. - `POST /workflows/generate` builds a definition from a plain-English prompt — see [Generate a cascade with AI](/docs/guides/ai-generate-a-cascade). ### Activation is gated On an unapproved (sandbox) account a new cascade is always saved as a draft, whatever `active` says, and the response carries a `note` explaining that; `PATCH` with `active: true` is refused with `403 activation_forbidden`. Drafts still run in test mode. Once the account is approved, `active: true` works and live runs become possible. See [Sandbox vs live](/docs/concepts/sandbox-vs-live). ## Templates `GET /workflow_templates` is a read-only catalog of published cascades. Copy a template's `definition` into `POST /workflows` to start from something that works. ## Related - [Executions](/docs/concepts/executions) — running a cascade and reading the outcome - [`POST /workflows/{id}/trigger`](/docs/api/workflows/trigger-workflow) - [Human review](/docs/concepts/human-review) and [Adverse action](/docs/concepts/adverse-action) — what the branch placement means at runtime --- --- title: Executions description: The lifecycle of a run — every status, what terminal means, how to poll, cancel and re-evaluate, and where the report PDFs live. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/executions" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/concepts/executions.md --- # Executions An **execution** is one run of a cascade for one applicant. `POST /workflows/{id}/trigger` creates it and returns `202 Accepted` immediately; the work happens in the background. Everything you need afterwards — status, decision, per-step outputs, PDFs — hangs off `GET /executions/{id}`. ## What are the statuses? | Status | Meaning | Terminal? | |---|---|---| | `queued` | Created as a batch row, not yet dispatched | no | | `pending` | Accepted, waiting for a worker | no | | `running` | Nodes are executing | no | | `waiting` | Paused on a `Wait` node or an adverse-action waiting period | no | | `awaiting_review` | Paused at human review — someone must decide | no | | `completed` | Every node ran cleanly | yes | | `failed` | A node raised; `failed_steps` says which | yes | | `cancelled` | Stopped by `POST /executions/{id}/cancel` or a batch cancel | yes | | `skipped` | A batch row that was never run (cap or cancel) | yes | Poll until the status is terminal or `awaiting_review`. The review sub-state lives in `review_status`: `none`, `in_review`, `approved`, `denied`. ```json { "data": { "id": 1842, "object": "execution", "workflow_id": 27, "status": "completed", "execution_key": "api_client:12:rv-app-20491", "external_reference": "rv-app-20491", "needs_human_review": false, "review_status": "none", "started_at": "2026-05-08T18:41:02Z", "completed_at": "2026-05-08T18:41:05Z", "input_context": { "applicant": { "first_name": "Jordan" }, "execution_mode": "test" } }, "meta": { "request_id": "7d4c2f48-…" } } ``` ## How do I read the decision? `GET /executions/{id}` returns the detail view: identity, status, `input_context` (the payload you sent plus resolved fields). With the `executions:read:full` scope it also returns: - `step_outputs` — a map of node id → that node's output: the report fields a product node returned (`creditScore.fico9`, `searchSummary.recordsFound`, …), which branch an `IfElse` took and why, what an action did. - `failed_steps` — the nodes that errored, with the error class. `step_outputs` routinely contains SSNs, dates of birth, addresses and raw vendor responses. Request `executions:read:full` only for integrations that handle per-step PII appropriately. ## How do I poll well? - Poll `GET /executions/{id}` every 1–2 seconds; most test runs finish in a few seconds, live runs in seconds to a minute depending on the products. - Stop on a terminal status or `awaiting_review`. - Prefer webhooks in production: `execution.completed` and `execution.failed` arrive signed within seconds of the transition. See [Webhooks](/docs/concepts/webhooks). - Respect `Retry-After` on `429`. ## How do I list executions? `GET /executions` is scoped to the calling API client and supports `filter[workflow_id]`, `filter[status]` (CSV, e.g. `running,waiting`), `filter[needs_human_review]`, `filter[review_status]` (CSV), `filter[batch_execution_id]`, `filter[execution_key]` and `filter[external_reference]`. An unknown status value returns `422 validation_failed`. Pagination is described in [Pagination and filtering](/docs/concepts/pagination-and-filtering). ## Can I cancel a run? `POST /executions/{id}/cancel` stops a `pending`, `running` or `waiting` execution and answers `202`. A running execution finishes its in-flight node and stops before dispatching the next; nothing is rolled back. Cancelling a terminal execution returns `409 execution_not_cancellable`. Cancelled runs do **not** emit `execution.failed`. ## Can I run it again? `POST /executions/{id}/re_evaluations` creates a **new** execution that replays the source's `input_context` against the definition the source ran with — not the cascade's current definition — so you can reproduce a decision after editing the cascade. It needs an `Idempotency-Key` (scoped to the source execution), an active cascade (`409 workflow_inactive` otherwise), and production access for live sources. Returns `202` with the new execution. ## Where are the PDFs? - `GET /executions/{id}/report_artifacts` lists the credit-report PDFs generated for the run (id, filename, content type, byte size); `GET …/report_artifacts/{artifact_id}` downloads one. - `GET /executions/{id}/notice_artifacts` and `…/{artifact_id}` do the same for adverse-action notices. Listing needs `executions:read`; downloading needs `executions:read:full`. Artifact ids are scoped to their execution — an id from another run returns `404`. ## Related - [`POST /workflows/{id}/trigger`](/docs/api/workflows/trigger-workflow) and [`GET /executions/{id}`](/docs/api/executions/get-execution) - [Idempotency](/docs/concepts/idempotency) — why the key is required and what it protects - [Human review](/docs/concepts/human-review) — what `awaiting_review` means for your integration - [Batches](/docs/concepts/batches) — running many applicants at once --- --- title: Batches description: Run up to 500 applicants through one cascade in a single request, track per-row status, cancel, resume or retry, and export the results. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/batches" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: docs/concepts/batches.md --- # Batches A **batch** runs one cascade against many applicants. You submit the rows once; Cascades pre-creates a child execution per row in `queued` state and works through them in the background. Track progress on the batch, drill into rows with the child-execution list, and export everything when it is done. ## How do I create a batch? `POST /workflows/{id}/batches` with a `rows` array — **up to 500 rows per request** — and an `Idempotency-Key`. Each row carries its own `external_reference` and `applicant`: ```json { "rows": [ { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone" } }, { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park" } } ], "execution_mode": "test", "external_reference": "rentvine-batch-001" } ``` The response is `202 Accepted` with the batch. More than 500 rows is `422 validation_failed` (`rows: max 500 rows per batch`). The idempotency key is compared against a hash of the rows: the same key with the same rows returns the original batch, the same key with different rows returns `409 idempotency_conflict`. Batches need an **active** cascade, even in test mode (see the next section). `execution_mode: live` needs production access (`403 live_mode_forbidden`). Needs `executions:write`. ## Why does my batch return `409 workflow_inactive`? The cascade is a draft (`active: false`). A draft refuses every batch create with `409 workflow_inactive`, in test mode too, before the rows are checked. A single trigger is different: `POST /workflows/{id}/trigger` with `execution_mode: test` runs on a draft and answers `202`. A sandbox account (not yet approved for production) cannot activate a cascade, so its cascades stay drafts and every batch create returns this `409`. To run a batch: 1. Get the account approved for production through the activation form at [/activate](/activate). 2. Activate the cascade: turn on its Status toggle on the Cascades list in the app, or `PATCH /workflows/{id}` with `active: true` from a key made at [/api-keys](/api-keys). Docs keys cannot activate a cascade (`403 activation_forbidden`). 3. Create the batch. Test mode still works on an active cascade, so you can try it before you run live. Until then, run single test-mode triggers on the draft to check the cascade one applicant at a time. ## What are the batch statuses? | Status | Meaning | |---|---| | `pending` | Accepted, dispatch not started | | `running` | Rows are being processed | | `interrupted` | Processing stopped before every row finished (worker restart, streaming disconnect) — resumable | | `completed` | Every row reached a terminal status, including rows that failed (count them with `failed_count`) | | `cancelled` | Stopped by `POST /batches/{id}/cancel` | | `failed` | The batch itself stopped on an unexpected error. Rare; `filter[status]` does not accept it | `total_count`, `completed_count` and `failed_count` on the batch give you a progress bar without listing rows. ## How do I follow the rows? `GET /batches/{id}/executions` lists child executions in `row_index` order with each row's `status`, `retry_count`, `external_reference`, `needs_human_review` and `review_status`. Filter with `filter[status]=failed,skipped`. Each child is a normal execution: `GET /executions/{id}` returns its detail and `step_outputs`, and it emits its own `execution.completed` / `execution.failed` webhook with `batch_execution_id` set — so integrations that process batches as a whole can ignore per-row events and watch the batch instead. ## Cancel, resume, retry - `POST /batches/{id}/cancel` halts dispatch and marks queued and in-flight rows `cancelled`. Only `pending`, `running` and `interrupted` batches can be cancelled (`409 batch_not_cancellable`). - `POST /batches/{id}/resume` re-queues the remaining rows of an `interrupted` batch (`409 batch_not_resumable` otherwise). The cascade must still be active and live batches still need production access. - `POST /batches/{id}/retry_failed` re-queues every `failed` row under the per-row retry cap and every `skipped` row, clearing their stale terminal state (`409 batch_not_retryable` when there is nothing to retry). All three answer `202` with the batch. Resume and retry also answer `409 workflow_inactive` when the cascade was set back to a draft after the batch was created. ## How do I export results? | Endpoint | Returns | |---|---| | `GET /batches/{id}/exports.csv` | One row per child execution: identity, status, decision, headline report fields | | `GET /batches/{id}/exports.zip` | Error report, per-product CSVs, per-row JSON, and the report PDFs | | `GET /batches/{id}/adverse_action_notices.zip` | Every applicant's adverse-action notice PDFs (`404 not_found` if none exist yet) | Add `?include=all` to the CSV or ZIP to include re-evaluation revisions rather than only the latest state per applicant. All three are gated behind `executions:read:full` because the contents are raw applicant data. ## Related - [Executions](/docs/concepts/executions) — the per-row lifecycle - [Idempotency](/docs/concepts/idempotency) - [`POST /workflows/{id}/batches`](/docs/api/batches/create-batch) --- --- title: Human review description: How a cascade pauses for a reviewer, what awaiting_review means for your integration, and the endpoints to claim, decide, note and audit reviews. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/human-review" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/concepts/human-review.md --- # Human review An `IfElse` node can route an applicant to `human_review_steps` instead of passing or failing them outright — a borderline credit score, a record that needs a person's judgement. When that happens the execution stops at `status: awaiting_review` with `needs_human_review: true` and `review_status: in_review`. The nodes listed in the decision's `human_review_steps` run immediately (interim work such as a hold email or pre-generating an adverse-action draft); then the execution pauses, and neither `pass_steps` nor `fail_steps` runs until someone decides. Reviewers can decide in the Cascades app; the API exposes the same actions so you can build the review queue into your own tooling. ## What happens at a decision? `POST /executions/{id}/review_decisions` with `{ "decision": "approved" | "denied", "note": "…" }` records the decision and resumes the matching branch: - **`approved`** resumes down the decision's `pass_steps`. - **`denied`** resumes down the decision's `fail_steps`. An `AdverseAction` node placed in `human_review_steps` has already rendered its draft at flag time; the notice is sent when a reviewer sends it from the app, not by the decision itself (see [Adverse action](/docs/concepts/adverse-action)). Test-mode executions resume inline: the response is `200 OK` with the final state and `resume_enqueued: false`. Live-mode executions enqueue the resume and answer `202 Accepted` with `resume_enqueued: true`; poll `GET /executions/{id}` until `status` leaves `awaiting_review` / `waiting`, or wait for the webhook. If nobody has claimed the review, the decision claims it for the caller. Errors: | Code | When | |---|---| | `409 review_not_pending` | The execution is not waiting for a review | | `409 review_already_decided` | Someone already approved or denied it | | `403 review_assigned_elsewhere` | The review is claimed by another user | | `403 live_mode_forbidden` | Live execution, account without production access | ## Claiming and releasing `POST /executions/{id}/review_assignment` with `{ "assignee": "me" }` claims the review for the user behind your key; `{ "assignee": null }` releases it. The body field is `assignee` (not `action`) because Rails reserves that parameter name. Each API client maps to a single user, so cross-user assignment is not a meaningful operation here — teams with several reviewers use one client per reviewer. ## Notes and the audit trail - `POST /executions/{id}/review_notes` with `{ "note": "…" }` appends a `note_added` event (`201 Created`). - `GET /executions/{id}/review_events` returns the full trail, oldest first: `flagged`, `assigned`, `unassigned`, `approved`, `denied`, `note_added`, `re_evaluated`, and the adverse-action notice events `adverse_action_drafted`, `adverse_action_sent`, `adverse_action_skipped` and `adverse_action_replayed`, each with the actor and timestamp. This is what you reconstruct "who decided what and when" from for compliance. ## Deciding many at once `POST /executions/review_bulk_actions` applies `approve`, `deny` or `assign_to_me` to up to **25** execution ids in one request. It is always asynchronous (`202`) and returns `{ applied, enqueued, skipped }`, where `skipped` lists a reason per id that could not be processed: `not_awaiting_review`, `review_not_pending`, `assigned_elsewhere`, `live_mode_forbidden`, `already_assigned`, or `not_found`. Ids belonging to another tenant are reported as `not_found` so existence is never leaked. ## Finding what needs review `GET /executions?filter[needs_human_review]=true&filter[review_status]=in_review` is your queue. Each row carries `assigned_to_user_id` so you can show who holds it. ## Related - [Handle human review](/docs/guides/handle-human-review) — a worked integration - [Executions](/docs/concepts/executions) — the status lifecycle - [`POST /executions/{id}/review_decisions`](/docs/api/executions/submit-review-decision) --- --- title: Adverse action description: How an AdverseAction node fires, the notice stages and flows, the draft-first default, the webhook events it emits, and where the notice PDFs end up. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/adverse-action" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/concepts/adverse-action.md --- # Adverse action When a cascade declines an applicant on the basis of a consumer report, the FCRA requires an **adverse-action notice**. In Cascades that is an `Action` node with `action_type: "AdverseAction"`: it picks the reasons from the conditions that failed, renders the operator's notice template to a PDF, emails it to the applicant, and keeps the archive. The API sees the results as execution state, webhook events and notice artifacts. For the legal background and the operator's obligations, see [Adverse action notices](/docs/compliance/adverse-action-notices). ## Where does the node sit, and why does it matter? An `AdverseAction` node must be downstream of an `IfElse` decision, and its branch decides its mode: | Placement | Mode | Runs when | |---|---|---| | In the decision's `human_review_steps` | **Human review** | The decision flags the execution for review — the draft is rendered before the pause; a reviewer sends or skips it on denial | | In the decision's `fail_steps` | **Rejected** | The decision auto-fails the applicant | | In `pass_steps`, or not under any decision | invalid | The cascade will not save | ## Draft first, or auto-send? A decline never delivers a notice unattended unless the node opts in. The node's `send_strategy` is: - **`draft_first`** (default) — the executor parks an editable **draft** (reasons, recipient, rendered PDF) and the execution waits. An operator reviews and sends it from the app; sending records the denial and delivers in one step. Approving the applicant instead discards the draft. - **`auto_send`** — an explicit opt-in for unattended API and batch runs: the notice is generated and emailed immediately. Human-review mode pre-generates the draft when the execution is flagged, so the reviewer's **Deny** dialog already shows the pre-selected reasons and lets them edit or skip the notice (a skip is recorded as `notice_intentionally_skipped` in the audit trail, never as compliant). ## Which reasons go on the notice? The executor looks at the conditions that matched in the upstream decision and maps each condition's `field` to an FCRA reason key through a compliance-editable mapping (`fico` or `credit_score` → `credit_score_low`, `inquir` → `too_many_inquiries`, `eviction` → `eviction_record`, and so on). Thresholds are never encoded in the mapping — whatever threshold the operator wrote is the threshold. Reviewers can override the list before sending. The full reason vocabulary is listed on the [compliance page](/docs/compliance/adverse-action-notices#which-reasons-can-a-notice-carry). ## Notice stages and flows `flow` follows the cascade's use case and drives the stage sequence: | Flow | Stages | Notes | |---|---|---| | `employment` | `pre_adverse` → waiting period → `post_adverse` | Pre-adverse notice with a copy of the report and the Summary of Rights; the execution enters `waiting`; the waiting period is floored to 5 days; then the final notice | | `tenant_screening` | `final` | Single notice | | `credit_lending` | `final` | Single notice | ## Which webhooks fire? | Event | When | |---|---| | `adverse_action.triggered` | Notice handling began. Employment: pre-adverse sent and the wait scheduled. Single-notice flows: final notice sent | | `adverse_action.completed` | Obligations fulfilled. Employment: post-adverse sent on resume. Single-notice flows: same instant as `triggered` | | `adverse_action.failed` | A notice email could not be delivered | | `adverse_action.retry_scheduled` | Employment only: the post-adverse send failed and a retry is scheduled (`retry_at`) | Payloads carry `workflow_execution_id`, `node_id`, `external_reference`, `flow`, `notice_stage` and, where relevant, `sent_to` and `completed_at`. Details per event are under [Webhook events](/docs/api/webhooks). ## Where are the PDFs? - Per execution: `GET /executions/{id}/notice_artifacts` lists the notice PDFs; `GET …/notice_artifacts/{artifact_id}` downloads one (`executions:read:full`). - Per batch: `GET /batches/{id}/adverse_action_notices.zip` bundles every applicant's notices (`404` while none exist). The archive is immutable: a resend from the audit dashboard re-delivers the exact bytes that were sent, with a `[Resent]` subject prefix. ## Templates Notice templates are per operator, versioned (each upload is an immutable version), and can be assigned as the default for a bureau (`equifax`, `experian`, `transunion`, `lexisnexis`, `cic`) so the notice matches the report that drove the decision. Email subject and body are customisable with `{{applicant_name}}`, `{{notice_date}}` and `{{recipient}}` placeholders. Manage them at [/adverse_action_templates](/adverse_action_templates); there is no API for template management yet. ## Related - [Human review](/docs/concepts/human-review) - [Webhooks](/docs/concepts/webhooks) - [Adverse action notices (compliance)](/docs/compliance/adverse-action-notices) --- --- title: Webhooks description: Subscribe to execution and adverse-action events, verify the HMAC signature, handle retries idempotently, and rotate secrets without downtime. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/webhooks" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/concepts/webhooks.md --- # Webhooks Cascades runs asynchronously, so the reliable way to learn that a run finished, failed, or produced an adverse-action notice is to be told. Subscribe an HTTPS endpoint once and every matching event is delivered as a signed `POST`. ## Which events exist? | Event | Fires when | |---|---| | [`execution.completed`](/docs/api/webhooks/execution.completed) | An execution reached `completed`. Once per execution, including batch rows | | [`execution.failed`](/docs/api/webhooks/execution.failed) | An execution reached `failed`; the payload adds `first_error_message`. Not for cancellations | | [`adverse_action.triggered`](/docs/api/webhooks/adverse_action.triggered) | Adverse-action handling began | | [`adverse_action.completed`](/docs/api/webhooks/adverse_action.completed) | Adverse-action obligations were fulfilled | | [`adverse_action.failed`](/docs/api/webhooks/adverse_action.failed) | A notice email failed to deliver | | [`adverse_action.retry_scheduled`](/docs/api/webhooks/adverse_action.retry_scheduled) | A post-adverse notice retry was scheduled | Events are emitted for **API-triggered** executions only (runs started from the Cascades UI have no API client and produce no webhooks). Test-mode runs emit them too; `execution.*` payloads carry `execution_mode: "test"` (adverse-action payloads do not — look the execution up by `workflow_execution_id` and read `input_context.execution_mode`). ## How do I subscribe? ```bash curl -X POST "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.partner.example/cascades", "event_filter": ["execution.completed", "execution.failed"] }' ``` The `201` response includes `signing_secret` **exactly once**; store it. An empty `event_filter` subscribes to every event; unknown event names are `422 validation_failed`. The URL must be `http` or `https`, public (no private, loopback, link-local, metadata or `localhost` addresses) and free of userinfo. Needs `webhooks:write`. `PATCH /webhook_subscriptions/{id}` changes the URL, filter or `active` flag; `DELETE` removes it; `GET` never re-exposes the secret. ## What does a delivery look like? ```http POST /cascades HTTP/1.1 Host: hooks.partner.example Content-Type: application/json User-Agent: Cascades-Webhook/1.0 X-Cascades-Event: execution.completed X-Cascades-Delivery: 9081 X-Cascades-Signature: t=1715191234,v1=4f1b2… { "id": "6d1f1a5e-4a2b-4c7d-9e8f-0a1b2c3d4e5f", "event": "execution.completed", "created_at": "2026-09-09T14:03:21Z", "data": { "workflow_execution_id": 1842, "workflow_id": 27, "batch_execution_id": null, "external_reference": "rv-app-20491", "execution_mode": "test", "status": "completed", "review_status": "approved", "completed_at": "2026-09-09T14:03:21Z" } } ``` `id` is the envelope id (the same across retries of one event); `X-Cascades-Delivery` is the delivery row id — your idempotency key. Branch on `X-Cascades-Event`. ## How do I verify the signature? `X-Cascades-Signature` is `t=,v1=` where the HMAC is computed over `"."` with your `signing_secret`. Verify against the **raw** bytes — any reformatting changes the digest — compare in constant time, and reject timestamps outside your tolerance (60 seconds is a reasonable cap). Drop-in verifiers for Ruby, Python and Node are in [Verify webhook signatures](/docs/guides/verify-webhooks). ### Rotating the secret `POST /webhook_subscriptions/{id}/rotate_secret` mints a new secret and returns it once. For the next **24 hours** every delivery is signed with both: `v1=` with the new secret and `v0=` with the old one. A verifier that accepts a match on *any* `v?=` value handles rotation with no deploy coordination; after the grace window only `v1=` ships. ## What if my endpoint is down? Any non-2xx response, connection error or timeout (5 s connect, 10 s read) is a failure. Cascades retries with backoff: ```text attempt 1 → +1m → 2 → +5m → 3 → +30m → 4 → +2h → 5 → +8h → 6 → +24h → dead ``` After six attempts the delivery is `dead`. `GET /webhook_deliveries` shows every delivery with `status` (`pending`, `retrying`, `succeeded`, `failed`, `dead`), `attempts`, `last_response_status` and `next_retry_at` — filter by subscription, event type or status. `POST /webhook_deliveries/{id}/retry` re-enqueues a `failed` or `dead` row immediately; an already-succeeded one is `409 delivery_not_retryable`. The delivery payload itself is not exposed through the API; re-read the execution instead. Your endpoint should return `2xx` as soon as the event is durably stored, do the real work on a queue, be idempotent on `X-Cascades-Delivery`, and answer within 10 seconds. ## Related - [Webhook events reference](/docs/api/webhooks) — payload schema per event - [Verify webhook signatures](/docs/guides/verify-webhooks) - [Executions](/docs/concepts/executions) and [Adverse action](/docs/concepts/adverse-action) --- --- title: Idempotency description: Which endpoints require an Idempotency-Key, how the key is scoped, what happens on replay and on conflict, and how to choose good keys. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/idempotency" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/concepts/idempotency.md --- # Idempotency A screening run pulls reports and may bill. A retried request must therefore never start a second run. Every endpoint that creates a run requires an `Idempotency-Key` header and uses it to return the original result on replay. ## Which endpoints require a key? | Endpoint | Key scope | |---|---| | `POST /workflows/{id}/trigger` | Per API client (the key is stored as `execution_key = api_client::`) | | `POST /workflows/{id}/batches` | Per API client, compared against a SHA-256 of the `rows` JSON | | `POST /executions/{id}/re_evaluations` | Per source execution (`reeval::`) | Omitting the header returns `422 missing_idempotency_key`. The header value is limited to 200 characters (the trigger namespace prefix consumes some of the stored key, so keep values comfortably shorter). ## What happens on replay? - **Same key, same body** → the original response, again: `202 Accepted` with the same execution or batch. Nothing new is created. - **Same key, different body** → `409 idempotency_conflict`. The stored request is not overwritten. A trigger answers with the message below; a batch says "…with a different request." instead. - **Concurrent identical requests** are race-safe: both callers receive the same execution; the loser of the insert race re-reads the winner's row. ```json { "error": { "code": "idempotency_conflict", "message": "The Idempotency-Key has already been used with a different request payload.", "details": [], "request_id": "7d4c2f48-…", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/idempotency_conflict" } } ``` ## How do I choose a key? Derive it from the upstream business event, not from the attempt: an application id, an order id, a submission id. Then a retry from a timeout, a crashed worker or a redeploy naturally carries the same key. Never reuse a key across distinct events, and never generate a fresh UUID per attempt — that defeats the protection entirely. ```http Idempotency-Key: rv-app-20491 # good: the application id Idempotency-Key: 3fa85f64-… # fine, if you persist it before the first attempt Idempotency-Key: $(uuidgen) # bad: every retry is a new run ``` The same discipline applies to `external_reference`: it is not an idempotency key (two triggers with the same reference create two runs), but it is what you correlate results and webhooks back to. ## Related - [Executions](/docs/concepts/executions) - [Batches](/docs/concepts/batches) - [`POST /workflows/{id}/trigger`](/docs/api/workflows/trigger-workflow) --- --- title: Pagination and filtering description: page and per_page, the meta block on every collection, filter[...] parameters including CSV values, and which lists are not paginated. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/pagination-and-filtering" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/concepts/pagination-and-filtering.md --- # Pagination and filtering Every list endpoint pages the same way and reports the same metadata, so one client helper covers all of them. ## How do I page? `page` (default `1`) and `per_page` (default `25`, maximum `100` — larger values are clamped, not rejected). A `page` past the end is clamped to the last page rather than returning an empty array; a non-numeric or negative `page` is treated as `1`. ```http GET /api/v1/executions?page=2&per_page=50 ``` ```json { "data": [ … ], "meta": { "page": 2, "per_page": 50, "total_count": 142, "total_pages": 3, "object": "execution", "request_id": "7d4c2f48-…" } } ``` Pagination is offset-based; there are no cursor or `Link` headers. Sort order is fixed per endpoint (newest first for most lists, `row_index` order for batch rows, `created_at` ascending for review events). ## How do I filter? Filters are `filter[]` query parameters. Where a field accepts several values, pass them as a comma-separated list: ```http GET /api/v1/executions?filter[status]=running,waiting&filter[workflow_id]=27 GET /api/v1/webhook_deliveries?filter[status]=failed,dead GET /api/v1/workflows?filter[active]=true ``` | Endpoint | Filters | |---|---| | `GET /workflows` | `active`, `use_case`, `trigger_type` | | `GET /workflow_templates` | `featured`, `category` | | `GET /executions` | `workflow_id`, `status` (CSV), `needs_human_review`, `review_status` (CSV), `batch_execution_id`, `execution_key`, `external_reference` | | `GET /batches` | `status` (CSV), `workflow_id`, `external_reference` | | `GET /batches/{id}/executions` | `status` (CSV) | | `GET /webhook_deliveries` | `webhook_subscription_id`, `event_type`, `status` (CSV) | CSV filters are validated: a value outside the allowed set (an execution status that does not exist, say) returns `422 validation_failed` with a `details` entry naming the field. Booleans accept `true`/`false`. ## Which lists are not paginated? Artifact lists (`GET /executions/{id}/report_artifacts`, `…/notice_artifacts`) and `GET /workflows/{id}/test_cases` return the complete collection in one response; `page` and `per_page` are ignored there. ## Related - [Executions](/docs/concepts/executions) - [Keys, scopes and rate limits](/docs/concepts/scopes-and-keys) — stay under the per-minute limit when walking a large collection --- --- title: Keys, scopes and rate limits description: How API keys are issued and expire, what each scope grants, how tokens map to your data, the rate limits, and the 429 envelope. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/scopes-and-keys" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/concepts/scopes-and-keys.md --- # Keys, scopes and rate limits ## How do I get a key? Mint one at [/api-keys](/api-keys) with **Generate key**. The raw token is a 36-character UUID (for example `3fa85f64-5717-4562-b3fc-2c963f66afa6`) and is shown **once**; Cascades stores only a SHA-256 digest and the first eight characters (the *prefix*, used for rate limiting and in logs). Keys expire after **90 days** by default. **Regenerate key** on the same page revokes your current key and shows a new one, so your account holds one key at a time; revoking from that page invalidates your keys at once. Send the key as a bearer token: ```http Authorization: Bearer {{API_KEY}} ``` A missing, malformed, expired or revoked token returns `401 unauthorized` with a `WWW-Authenticate: Bearer` challenge. Repeatedly presenting unknown tokens gets the source IP banned for a while. ## What does a key see? A key belongs to an **API client**, which belongs to a **user**. That chain decides visibility: - **Cascades and templates are user-level.** Every client of the same user sees the same cascades. - **Executions, batches, webhook subscriptions and deliveries are client-level.** Two clients of one user cannot read each other's runs or subscriptions. Requests for a record you cannot see return `404 not_found` — the API never confirms that another tenant's id exists. ## Which scopes exist? | Scope | Grants | |---|---| | `workflows:read` | `GET /workflows`, `GET /workflows/{id}`, `GET /workflows/{id}/test_cases` | | `workflows:write` | Create, update, delete and generate cascades | | `workflow_templates:read` | The template catalog | | `executions:read` | List and show executions and batches, review events, artifact **lists** | | `executions:read:full` | Adds `step_outputs` and `failed_steps` to execution detail, and unlocks artifact **downloads** and batch exports — routinely contains SSNs, dates of birth, addresses and raw vendor responses | | `executions:write` | Trigger, cancel, re-evaluate, batches, review decisions, assignment, notes, bulk actions | | `webhooks:read` | List and show subscriptions and deliveries | | `webhooks:write` | Create, update, delete subscriptions, rotate secrets, retry deliveries | Keys minted at [/api-keys](/api-keys) carry all eight. A request that needs a scope the key lacks returns `403 insufficient_scope` with the missing scope in `details`. Request the narrowest set your integration needs when CRS issues production keys; in particular, ask for `executions:read:full` only when your flow handles per-step PII appropriately. ## What are the rate limits? | Limit | Scope | |---|---| | 60 requests / minute | per source IP, on `/api/v1/*` | | 600 requests / hour | per token prefix | | 300 requests / 5 minutes | per source IP, across the whole host | A throttled request answers `429` with the standard error envelope and a `Retry-After` header. The message is fixed; the number of seconds until the window resets is only in the header, so read `Retry-After`, never the message: ```http HTTP/1.1 429 Too Many Requests Retry-After: 12 Content-Type: application/json; charset=utf-8 ``` ```json { "error": { "code": "rate_limited", "message": "Rate limit exceeded. Retry later.", "details": [], "request_id": "7d4c2f48-…", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/rate_limited" } } ``` Honour `Retry-After`; when it is absent, back off exponentially from one second. Prefer webhooks over tight polling loops, and batches over hundreds of single triggers. ## Keys used on these docs When you are signed in, the key filled into the samples here is a separate, one-hour, **sandbox-only** token minted for the docs Try-it console. It cannot make live requests even on an approved account, and it is not the key to ship in your integration. ## Related - [Sandbox vs live](/docs/concepts/sandbox-vs-live) - [Errors](/docs/api/errors) — every code with causes and fixes - [`GET /workflows`](/docs/api/workflows/list-workflows) --- --- title: Glossary description: Definitions of the terms the Cascades API docs use — cascade, node, execution, step outputs, batch, decision, scope, idempotency key, webhook event. canonical_url: "https://cascades.crscreditapi.com/docs/concepts/glossary" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/concepts/glossary.md --- # Glossary One definition per term, each linking to the page that covers it in depth. The reference pages, the code samples and the MCP tool manifest all use these words with exactly these meanings. ## Cascades and runs **Cascade** (API resource: `workflow`) — a decision workflow: which reports to pull, in what order, which rules to evaluate, and what happens on each outcome. The visual builder and the API expose the same object; over the API it is named `workflow` and its node graph lives in `definition`. See [Cascades](/docs/concepts/cascades) and the [Workflow schema](/docs/api/schemas/Workflow). **Workflow** — the API name for a cascade. `GET /workflows` lists them; `active: false` marks a draft that can run once in test mode but not live. See [Cascades](/docs/concepts/cascades). **Node** — one step in a cascade's `definition`: an `ApplicantSource` entry point, a `BillingProduct` that pulls one report, an `IfElse` that branches, a `Wait`, a `GoTo`, an `Action` (approve, email, adverse action, notification) or a terminal `ShowResults`. Nodes are wired by edge keys such as `next_step`, `pass_steps`, `human_review_steps` and `fail_steps`. See [What is in the definition?](/docs/concepts/cascades#what-is-in-the-definition) and the [CascadeNode schema](/docs/api/schemas/CascadeNode). **Execution** — one run of a cascade for one applicant. `POST /workflows/{id}/trigger` creates it and answers `202 Accepted`; the work happens in the background and everything about it hangs off `GET /executions/{id}`. See [Executions](/docs/concepts/executions) and the [Execution schema](/docs/api/schemas/Execution). **Step outputs** (`step_outputs`) — a map of node id to that node's output on an execution: the report fields a product node returned, which branch an `IfElse` took and why, what an action did. Returned only to keys with the `executions:read:full` scope because it routinely contains per-step PII. See [How do I read the decision?](/docs/concepts/executions#how-do-i-read-the-decision). **Batch** — one cascade run against many applicants in a single request. Cascades pre-creates a child execution per row and works through them in the background; the batch tracks progress and exports the results. See [Batches](/docs/concepts/batches) and the [Batch schema](/docs/api/schemas/Batch). **Decision** — the outcome an `IfElse` node reaches for an applicant: pass, fail or send to human review, each resuming its own branch of nodes. The execution's final status, `step_outputs` and any adverse-action notice are the API view of that decision; the operator app and the tenant-screening ScoreCard present the same run as a summary page. See [Executions](/docs/concepts/executions) and [Human review](/docs/concepts/human-review). ## Modes and access **Sandbox vs live** — the two hosts and the two ways a run can execute. Every run carries an `execution_mode` of `test` (synthetic mock data, nothing billed) or `live` (real consumer reports, billable, requires an approved account and an active cascade). See [Sandbox vs live](/docs/concepts/sandbox-vs-live). **`execution_mode`** — the per-request field that selects `test` or `live`. It is chosen per trigger, not per key, so the same integration moves to production by changing one value; keys minted from these docs are pinned to `test`. See [Sandbox vs live](/docs/concepts/sandbox-vs-live) and the [TriggerRequest schema](/docs/api/schemas/TriggerRequest). **API key** — the bearer token sent as `Authorization: Bearer ` on every request. Keys are minted at [/api-keys](/api-keys), carry a set of scopes, and see only the workflows, executions and subscriptions of the account that issued them. See [Keys, scopes and rate limits](/docs/concepts/scopes-and-keys). **Scope** — a permission attached to an API key, such as `workflows:read`, `executions:write` or `executions:read:full`. A request that needs a scope the key lacks returns `403 insufficient_scope`; each operation's reference page lists the scopes it requires. See [Which scopes exist?](/docs/concepts/scopes-and-keys#which-scopes-exist). **Idempotency key** — the `Idempotency-Key` header required by every endpoint that creates a run (`trigger`, `batches`, `re_evaluations`). A retry with the same key and body returns the original result instead of starting a second run; the same key with a different body is `409 idempotency_conflict`. See [Idempotency](/docs/concepts/idempotency). **Test identity** (registered test subject) — a synthetic applicant authored in the CRS sandbox test-case registry with a specific history. `GET /workflows/{id}/test_cases` returns the subjects that match a cascade's products with ready-to-send payloads; a test-mode trigger for a subject registered for every product node in the cascade replays its authored reports, and other test runs return schema mocks. See [Test identities](/docs/sandbox/test-identities). **Registry** — the CRS sandbox test-case registry that holds the test identities and the bureau responses replayed for them. See [Test identities](/docs/sandbox/test-identities). ## Events and notices **Webhook event** — a named change Cascades delivers to your endpoint as an HTTPS `POST` with a JSON envelope `{ id, event, created_at, data }`, for example `execution.completed` or `adverse_action.sent`. Subscriptions choose which events they receive. See [Webhooks](/docs/concepts/webhooks) and the [webhook events reference](/docs/api/webhooks). **Signing secret** — the per-subscription secret returned once by `POST /webhook_subscriptions`. Every delivery is signed with it (`X-Cascades-Signature: t=,v1=`), and `rotate_secret` replaces it with a 24-hour grace window. See [Verify webhook signatures](/docs/guides/verify-webhooks). **Adverse action** — the FCRA notice owed to an applicant declined on the basis of a consumer report. In Cascades it is an `Action` node with `action_type: "AdverseAction"` that picks the reasons, renders the notice PDF, emails it and archives it, either automatically or as a draft a reviewer sends. See [Adverse action](/docs/concepts/adverse-action) and [Adverse action notices](/docs/compliance/adverse-action-notices). **Human review** — the branch an `IfElse` node can route to instead of pass or fail. The execution pauses at `status: awaiting_review` until a reviewer posts a decision, which resumes `pass_steps` (approved) or `fail_steps` (denied). See [Human review](/docs/concepts/human-review). ## Reference and tooling **MCP server** — the Model Context Protocol server that exposes the Cascades API to AI assistants as tools. It is a thin wrapper: every tool is exactly one authenticated REST call using your own API key and scopes. See [MCP server](/docs/mcp). **`operationId`** — the stable identifier of one operation in the OpenAPI spec, such as `triggerWorkflow` or `getExecution`. It names the reference page (`/docs/api/workflows/trigger-workflow`), the generated code samples and the matching MCP tool, and it is the id to quote in error reports. See the [API reference](/docs/api) and [/openapi.json](/openapi.json). **OpenAPI spec** — the machine-readable contract the reference pages, code samples and MCP manifest are generated from, served at [/openapi.json](/openapi.json) and [/openapi.yaml](/openapi.yaml). See the [API reference](/docs/api). ## Related - [Overview](/docs) — what the API does and how to integrate, in order - [API reference](/docs/api) — every operation, schema, error code and webhook event - [llms.txt](/llms.txt) — the machine-readable index of these docs --- --- title: Your first screening description: Step by step from a fresh sandbox key to a decided execution — trigger a test subject, pick the mock outcome, poll, handle review, and read the notice. canonical_url: "https://cascades.crscreditapi.com/docs/guides/first-screening" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/guides/first-screening.md.erb --- # Your first screening This guide runs one applicant through a cascade end to end in the sandbox: pick a registered test subject for a realistic payload, choose the mock outcome, trigger, poll, decide a human review if the cascade pauses, and read what came out. Everything is `execution_mode: test`, so nothing is billed and no bureau is called. > **Before you start** > > You need a sandbox key from [/api-keys](/api-keys) and at least one cascade in your workspace (build one at [/cascades](/cascades) or [generate one](/docs/guides/ai-generate-a-cascade)). Drafts are fine — a draft accepts test-mode triggers. ### Pick the cascade and a test subject List your cascades and note an `id`. Then ask for the subjects registered for that cascade's products — each comes with a `payload` shaped for the trigger endpoint: **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/27/test_cases") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List sandbox test cases for a workflow 27 and summarise the result." # It will call the `cascades_list_test_cases` tool with your key. ``` Pick any subject. When the subject is registered for every product node in your cascade, the test run replays its authored history (a borderline credit file, a criminal record, an eviction) at no charge, and `input_context.sandbox_replay` is `true`; otherwise every product node returns its schema mock. See [Test identities](/docs/sandbox/test-identities). If the list is empty the registry could not be reached or the cascade has no covered products; any hand-written applicant works just as well in test mode. ### Trigger the run Send the subject's `payload` as the trigger body with a unique `Idempotency-Key` (use your own application id) and an `external_reference` you can correlate on later: **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/workflows/27/trigger" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -d '{ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27/trigger", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, body: JSON.stringify({ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }) }); 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/27/trigger", headers={ "Authorization": "Bearer {{API_KEY}}", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6", }, json={ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }, ) 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/27/trigger") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request["Idempotency-Key"] = "3fa85f64-5717-4562-b3fc-2c963f66afa6" request.body = <<~JSON { "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" } 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(`{ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/workflows/27/trigger", bytes.NewBuffer(body)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") req.Header.Set("Content-Type", "application/json") req.Header.Set("Idempotency-Key", "3fa85f64-5717-4562-b3fc-2c963f66afa6") 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: # "Trigger a workflow 27 and summarise the result." # It will call the `cascades_trigger_workflow` tool with your key. ``` For an applicant that is not a replayed subject, every product node returns its clean `happy_path` mock by default and the applicant is approved. To walk the decline path in test mode, add `"scenario": "fail_path"` to `payload` — every product node then returns its decline mock (the eviction node, for example, reports two filings and `recommendation: FAIL`): ```json { "payload": { "scenario": "fail_path", "applicant": { "first_name": "Raymond", "last_name": "Okafor", "ssn": "…", "dob": "1982-01-01" }, "property": { "address": "1803 Norma", "city": "Cottonwood", "state": "CA", "zip": "91502" } }, "execution_mode": "test", "external_reference": "rv-app-20493" } ``` Expect `202 Accepted`. Keep `data.id` — that is the execution — and `meta.request_id` for support. > Try it in the browser: https://cascades.crscreditapi.com/docs/api/workflows/trigger-workflow — a signed-in sandbox key runs `POST /workflows/{id}/trigger` against the test identities. ### Poll until it settles Poll every second or two until `status` is `completed`, `failed`, `cancelled`, `skipped`, or `awaiting_review`: **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/executions/1842" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show an execution 1842 and summarise the result." # It will call the `cascades_get_execution` tool with your key. ``` With `executions:read:full` the response includes `step_outputs`: one entry per node, keyed by node id, holding the report fields the decision used (for the credit subject, `creditScore.fico9` and the inquiry counts) and which branch each `IfElse` took. ```json { "status": "completed", "step_outputs": { "credit": { "creditScore.fico9": 660, "summary.recentInquiries": 0, "recommendation": "PASS" }, "decide": { "matched_branch": "pass", "conditions": [{ "field": "creditScore.fico9", "operator": "greater_than_or_equal", "value": 640, "result": true }] }, "approve": { "decision": "approved" } } } ``` ### If it pauses: decide the review A cascade with a human-review branch stops at `awaiting_review`. Approve or deny it — in test mode the branch runs inline and the response is the final state: **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }) }); 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/executions/1842/review_decisions", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }, ) 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/executions/1842/review_decisions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "decision": "approved", "note": "Manual review confirmed; OFAC clean." } 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(`{ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions", 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: # "Approve or deny a paused review 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/review_decisions with curl. ``` `approved` continues down `pass_steps`; `denied` continues down `fail_steps`; an adverse-action node in `human_review_steps` prepared its notice draft when the run was flagged. The whole decision trail is at `GET /executions/{id}/review_events`. More in [Handle human review](/docs/guides/handle-human-review). ### Read the artifacts If the cascade pulled a credit report, its PDF is listed at `GET /executions/{id}/report_artifacts`; if it declined the applicant with an adverse-action node, the notice PDFs are at `GET /executions/{id}/notice_artifacts`. Listing needs `executions:read`; downloading a file needs `executions:read:full`. **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842/notice_artifacts") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List adverse-action notice pdfs for an execution 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions/{id}/notice_artifacts with curl. ``` ## Where to go from here - Replace polling with a [webhook subscription](/docs/concepts/webhooks) — `execution.completed` arrives seconds after the transition. - Run many applicants at once with a [batch](/docs/concepts/batches). - When your account is approved, flip `execution_mode` to `live`: [Sandbox vs live](/docs/concepts/sandbox-vs-live). --- --- title: Verify webhook signatures description: Drop-in Ruby, Python and Node verifiers for X-Cascades-Signature, with timestamp tolerance, constant-time comparison and secret-rotation support. canonical_url: "https://cascades.crscreditapi.com/docs/guides/verify-webhooks" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/guides/verify-webhooks.md --- # Verify webhook signatures Every webhook delivery carries `X-Cascades-Signature: t=,v1=` where `v1` is `HMAC-SHA256(signing_secret, ".")`. During the 24 hours after a secret rotation the header also carries `v0=`, signed with the previous secret. Each verifier below accepts the request if **any** `v?=` value matches, so it handles steady state and rotation without changes. ## What must the verifier do? 1. Parse the header into `t` and the `v…` values. 2. Reject if `t` is missing or further than your tolerance from now (60 seconds is a reasonable cap) — this defeats replay. 3. Compute the HMAC over the **raw** request body bytes. Do not parse and re-serialise the JSON first; any reformatting changes the digest. 4. Compare with a constant-time function. 5. Only then parse the body and act on it — and dedupe on `X-Cascades-Delivery`, because retries re-deliver the same event. ## Ruby ```ruby require "openssl" def verify_cascades_signature(header, raw_body, secret, tolerance: 60) parts = header.to_s.split(",").map { |p| p.split("=", 2) }.to_h ts = parts["t"].to_i return false if ts == 0 return false if (Time.now.to_i - ts).abs > tolerance expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{ts}.#{raw_body}") parts.any? do |k, v| k.start_with?("v") && OpenSSL.secure_compare(expected, v) end end ``` This is plain Ruby: `OpenSSL.secure_compare` ships with Ruby 3.0 and later, so it runs in a Rails app and outside one. In a Rails controller, the raw body is `request.raw_post`. ```ruby class CascadesWebhooksController < ApplicationController skip_before_action :verify_authenticity_token def create unless verify_cascades_signature(request.headers["X-Cascades-Signature"], request.raw_post, ENV.fetch("CASCADES_WEBHOOK_SECRET")) return head :unauthorized end delivery_id = request.headers["X-Cascades-Delivery"] ProcessCascadesEventJob.perform_later(delivery_id, JSON.parse(request.raw_post)) unless Event.exists?(delivery_id: delivery_id) head :ok end end ``` ## Python ```python import hmac import hashlib import time def verify_cascades_signature(header, raw_body, secret, tolerance=60): parts = dict(p.split("=", 1) for p in header.split(",")) ts = int(parts.get("t", 0)) if ts == 0 or abs(int(time.time()) - ts) > tolerance: return False signed = f"{ts}.{raw_body}".encode() expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return any( k.startswith("v") and hmac.compare_digest(expected, v) for k, v in parts.items() ) ``` With Flask, `raw_body` is `request.get_data(as_text=True)`; with FastAPI, `(await request.body()).decode()`. ## Node.js ```js const crypto = require("crypto"); function verifyCascadesSignature(header, rawBody, secret, tolerance = 60) { const parts = Object.fromEntries(header.split(",").map(p => p.split("="))); const ts = parseInt(parts.t, 10); if (!ts) return false; if (Math.abs(Math.floor(Date.now() / 1000) - ts) > tolerance) return false; const expected = crypto .createHmac("sha256", secret) .update(`${ts}.${rawBody}`) .digest("hex"); const expBuf = Buffer.from(expected, "hex"); return Object.entries(parts).some(([k, v]) => { if (!k.startsWith("v")) return false; const sigBuf = Buffer.from(v, "hex"); return sigBuf.length === expBuf.length && crypto.timingSafeEqual(expBuf, sigBuf); }); } ``` With Express, mount the route with `express.raw({ type: "application/json" })` so `req.body` is the untouched `Buffer`, and pass `req.body.toString("utf8")`. ## Testing your verifier Sign a body yourself with the secret from `POST /webhook_subscriptions` and post it to your endpoint: ```bash BODY='{"id":"test","event":"execution.completed","created_at":"2026-09-09T14:03:21Z","data":{}}' TS=$(date +%s) SIG=$(printf '%s.%s' "$TS" "$BODY" | openssl dgst -sha256 -hmac "$CASCADES_WEBHOOK_SECRET" | awk '{print $2}') curl -X POST https://hooks.partner.example/cascades \ -H "Content-Type: application/json" \ -H "X-Cascades-Event: execution.completed" \ -H "X-Cascades-Delivery: 1" \ -H "X-Cascades-Signature: t=$TS,v1=$SIG" \ --data-raw "$BODY" ``` Then trigger a test-mode run and watch `GET /webhook_deliveries` for the real delivery's `status` and `last_response_status`. ## Related - [Webhooks](/docs/concepts/webhooks) — subscriptions, retries, rotation - [Webhook events reference](/docs/api/webhooks) — payload per event --- --- title: Handle human review description: Build a review queue on the API — find paused executions, claim them, approve or deny with a note, act in bulk, and reconcile with the audit trail. canonical_url: "https://cascades.crscreditapi.com/docs/guides/handle-human-review" doc_version: "1.0.0" last_updated: "2026-10-05T19:24:37Z" source: docs/guides/handle-human-review.md.erb --- # Handle human review When a cascade routes an applicant to human review, the execution stops at `awaiting_review` and waits. This guide builds the loop your reviewers need: a queue, a claim, a decision with a note, and the trail to prove it. The concepts are in [Human review](/docs/concepts/human-review). ### Find what is waiting Executions that need a person are `needs_human_review: true` with `review_status: in_review`. Filter for them, newest first: **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/executions" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List executions using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions with curl. ``` Add `filter[workflow_id]` to build one queue per cascade, and use `assigned_to_user_id` to show who holds each item. Poll this list, or maintain it from `execution.*` webhooks plus a status check. ### Claim the review Claim before you show the case to a reviewer so two people never decide the same execution: **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/executions/1842/review_assignment" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "assignee": "me" }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_assignment", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "assignee": "me" }) }); 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/executions/1842/review_assignment", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "assignee": "me" }, ) 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/executions/1842/review_assignment") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "assignee": "me" } 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(`{ "assignee": "me" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_assignment", 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: # "Claim or unassign a review 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/review_assignment with curl. ``` `{ "assignee": "me" }` claims for the user behind the key; `{ "assignee": null }` releases. A review held by someone else answers `403 review_assigned_elsewhere` on decision. Each API client maps to one user, so give each reviewer their own client if you need per-person attribution. ### Show the reviewer the evidence `GET /executions/{id}` with `executions:read:full` returns `step_outputs` — the report fields and the conditions that sent the applicant to review — and `GET /executions/{id}/report_artifacts` lists the report PDFs. That is what a reviewer reads before deciding. ### Record notes as you go Notes become `note_added` events in the audit trail; they are not editable afterwards: **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/executions/1842/review_notes" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "note": "Pulled credit report manually; matches OFAC cleanly." }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_notes", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "note": "Pulled credit report manually; matches OFAC cleanly." }) }); 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/executions/1842/review_notes", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "note": "Pulled credit report manually; matches OFAC cleanly." }, ) 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/executions/1842/review_notes") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "note": "Pulled credit report manually; matches OFAC cleanly." } 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(`{ "note": "Pulled credit report manually; matches OFAC cleanly." }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_notes", 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: # "Append a review note 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/review_notes with curl. ``` ### Approve or deny **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }) }); 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/executions/1842/review_decisions", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }, ) 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/executions/1842/review_decisions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "decision": "approved", "note": "Manual review confirmed; OFAC clean." } 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(`{ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions", 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: # "Approve or deny a paused review 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/review_decisions with curl. ``` - `approved` resumes down the decision's `pass_steps`. - `denied` resumes down the decision's `fail_steps`. If an adverse-action node lives in `human_review_steps`, its draft was already prepared when the execution was flagged; `auto_send` does not apply to that placement — a person sends the notice from the app. In test mode the branch runs inline and you get `200` with the final state. In live mode you get `202` with `resume_enqueued: true`; poll until `status` leaves `awaiting_review`, or wait for `execution.completed`. A second decision on the same review is `409 review_already_decided`. ### Reconcile with the audit trail **cURL** ```shell curl "https://cascades.crscreditapi.com/api/v1/executions/1842/review_events" \ -H "Authorization: Bearer {{API_KEY}}" ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_events", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` **Python** ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842/review_events", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842/review_events") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_events", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List review audit events 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions/{id}/review_events with curl. ``` Events arrive oldest first: `flagged` (when the cascade paused), `assigned` / `unassigned`, `note_added`, `approved` or `denied`, and `re_evaluated` if the run was replayed. When an adverse-action node parks a notice draft for review, the trail adds `adverse_action_drafted`, then `adverse_action_sent` or `adverse_action_skipped`; `adverse_action_replayed` marks a sent notice that was resent from the app. Each carries `actor_user_id` and `created_at`. Store the trail with your own case record; it is the record of who decided what and when. ## Deciding in bulk For end-of-day sweeps, `POST /executions/review_bulk_actions` applies `approve`, `deny` or `assign_to_me` to up to 25 ids and reports what it could not do: **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/executions/review_bulk_actions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/review_bulk_actions", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }) }); 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/executions/review_bulk_actions", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }, ) 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/executions/review_bulk_actions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." } 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(`{ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/review_bulk_actions", 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: # "Bulk approve / deny / assign_to_me using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/review_bulk_actions with curl. ``` ```json { "data": { "object": "bulk_review_result", "applied": 4, "enqueued": 4, "skipped": [ { "id": 1846, "reason": "assigned_elsewhere" } ] }, "meta": { "request_id": "…" } } ``` Bulk decisions are always asynchronous, even in test mode. Ids from another tenant come back as `not_found`. ## Related - [Human review](/docs/concepts/human-review) - [Adverse action](/docs/concepts/adverse-action) — what a denial triggers --- --- title: Generate a cascade with AI description: Turn a plain-English screening policy into a valid cascade with POST /workflows/generate, preview before saving, and handle the busy and failure cases. canonical_url: "https://cascades.crscreditapi.com/docs/guides/ai-generate-a-cascade" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/guides/ai-generate-a-cascade.md.erb --- # Generate a cascade with AI `POST /workflows/generate` takes a description of your screening policy and returns a cascade definition built by Claude, validated against the same node grammar the visual builder enforces. Use it to draft a cascade from a sentence, then refine in the builder or with `PATCH /workflows/{id}`. ## What do I send? A `prompt` describing the policy in the language you would use with a colleague. Be explicit about products, thresholds, and what happens on each outcome: **cURL** ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/workflows/generate" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }' ``` **Node.js** ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/generate", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }) }); 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/generate", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }, ) 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/generate") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" } 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(`{ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/workflows/generate", 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: # "Generate a workflow from a prompt and summarise the result." # It will call the `cascades_generate_workflow` tool with your key. ``` Optional fields: `name` (otherwise the generator names it), `use_case` (defaults to `tenant_screening`), `active` (subject to the activation gate), and `create: false` to **preview** the definition without saving anything. ```json { "prompt": "Verify identity, pull a TransUnion credit report and a criminal search. Approve FICO 680 and above with no felonies. Send 620–679 to manual review. Decline below 620 or any felony, with an adverse-action notice.", "create": false } ``` ## What comes back? With `create: false`, `200` and a preview: ```json { "data": { "object": "cascade_generation", "definition": [ { "id": "start", "type": "ApplicantSource", "next_step": ["identity"] }, "…" ], "valid": true, "name": "Tenant screening — FICO 680 gate", "model": "claude-opus-4-8", "repaired": false, "warnings": [] }, "meta": { "request_id": "…" } } ``` By default the cascade is saved and the response is `201` with the workflow plus a `generation` block carrying the same `model`, `repaired` and `warnings` fields. `repaired: true` means the first draft failed validation and a cheaper model fixed it in one repair turn; read `warnings` — they list anything the generator could not express exactly (a product your account cannot use, an ambiguous threshold). On a sandbox account the cascade is saved as a **draft** and the response carries a `note` saying so. It still runs in test mode. ## Which errors should I handle? | Code | Meaning | What to do | |---|---|---| | `422 validation_failed` | Empty prompt, or longer than the cap | Trim the prompt | | `422 generation_failed` | The model could not produce a valid definition; `details` lists the validation errors | Rephrase — name products and thresholds explicitly | | `503 designer_busy` | The generator's concurrency cap is in use | Retry after a few seconds with backoff | | `403 insufficient_scope` | Key lacks `workflows:write` | Use a key with the scope | Generation is not idempotent: two identical prompts create two cascades. Use `create: false` when exploring. ## Iterating The returned `definition` is ordinary JSON. Edit nodes, thresholds or edges and send the whole array back with `PATCH /workflows/{id}`; the same validator runs on every save. To test it, trigger it in test mode with a [registered subject](/docs/sandbox/test-identities) and read `step_outputs` to see which branch fired. > **From an agent** > > The MCP server exposes this endpoint as `cascades_generate_workflow`, so in Claude Code or Cursor you can say *"Generate a cascade that pulls credit and eviction, approves FICO 660+ with no evictions, and declines the rest with a notice"* and then *"run it for a registered test subject"*. See [MCP server](/docs/mcp). ## Related - [Cascades](/docs/concepts/cascades) — the node grammar the generator targets - [`POST /workflows/generate`](/docs/api/workflows/generate-workflow) --- --- title: FCRA permissible purpose description: Why every live report pull needs an FCRA permissible purpose, which purposes CRS certifies accounts for, and what that allows you to run through the API. canonical_url: "https://cascades.crscreditapi.com/docs/compliance/fcra-permissible-purpose" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/compliance/fcra-permissible-purpose.md --- # FCRA permissible purpose > [!WARNING] > **This page is not legal advice.** It describes how Cascades applies the Fair Credit Reporting Act to API usage so your integration matches the certification your account holds. Your obligations under the FCRA, state law and the bureaus' terms are yours; consult your counsel. ## Why does it matter for an API call? A consumer report — a credit report, a criminal or eviction search, an identity verification against bureau data — may only be obtained for a **permissible purpose** listed in FCRA §604 (15 U.S.C. §1681b). Every `execution_mode: live` run that reaches a product node pulls such a report. The API cannot tell whether the applicant behind `payload.applicant` really applied for the tenancy, job or credit you certified; that certification is yours, and it is the reason live access is granted per account rather than per key. Test-mode runs use synthetic data or bureau-published sandbox subjects and are not consumer reports; they need no permissible purpose. ## Which purposes does CRS certify? At activation ([/activate](/activate)) you declare one permissible purpose and one industry, and sign the end-user certification. The purposes Cascades accepts: | Purpose (as declared) | FCRA basis | Typical use | |---|---|---| | Tenant / rental application | §604(a)(3)(F) — legitimate business need in connection with a transaction initiated by the consumer | Residential tenant screening | | Employment screening | §604(a)(3)(B), with the §604(b) employment conditions | Pre-employment and ongoing employment checks | | Extension of credit | §604(a)(3)(A) | Consumer lending, auto, commercial-with-guarantor | | Insurance underwriting | §604(a)(3)(C) | Underwriting of insurance involving the consumer | | Written instructions of the consumer | §604(a)(2) | Consumer-initiated checks with explicit written authorisation | | Legitimate business need | §604(a)(3)(F) | Account review and other consumer-initiated business transactions | Industries: Auto Lending, Bank & Credit Union, Bankruptcy Attorney, Commercial Lending, Consumer Lending, Debt Consolidation, Employment, Housing Counseling, Tenant Screening. Approval is granted by CRS after review of the activation form. Until then live requests return `403 live_mode_forbidden` — see [Sandbox vs live](/docs/concepts/sandbox-vs-live). ## What does the purpose constrain in practice? - **Run only cascades that fit the certified purpose.** An account certified for tenant screening should not run employment cascades live. All products remain available in the sandbox so you can build and test; live activation is reviewed against your certification. - **Employment has extra steps.** The §604(b) conditions require a clear, stand-alone disclosure and the applicant's written authorisation *before* the pull, and the two-step pre-adverse / adverse notice process if you decide against them. Cascades' employment flow implements the notice sequence (see [Adverse action](/docs/concepts/adverse-action)); the disclosure and authorisation happen on your side, before you call `trigger`. - **Keep the consumer's authorisation and the application record.** `external_reference` on the trigger is the place to carry your application id so a pull can be tied back to the transaction that justified it. - **Limit who can read reports.** `executions:read:full` unlocks per-step report data and PDF downloads. Grant it to the systems and people with a need to know, and see [Keys, scopes and rate limits](/docs/concepts/scopes-and-keys). - **Do not repurpose a report.** A report obtained for a tenancy decision may not be reused for an unrelated purpose. Re-evaluations (`POST /executions/{id}/re_evaluations`) replay the original run's input against the original definition; they do not create a new permissible purpose. ## What happens when I take adverse action? If you deny, or take another adverse action against, an applicant based in whole or in part on a consumer report, §615 requires a notice with the source of the report, the applicant's right to a free copy and to dispute, and (for credit) the key factors and score disclosure. Cascades' adverse-action node produces that notice; the requirements and the reason vocabulary are on [Adverse action notices](/docs/compliance/adverse-action-notices). ## Related - [Adverse action notices](/docs/compliance/adverse-action-notices) - [Sandbox vs live](/docs/concepts/sandbox-vs-live) - [Keys, scopes and rate limits](/docs/concepts/scopes-and-keys) --- --- title: Adverse action notices description: What an FCRA adverse-action notice must contain, how Cascades' single-notice and employment flows meet it, the reason vocabulary, and what stays yours. canonical_url: "https://cascades.crscreditapi.com/docs/compliance/adverse-action-notices" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/compliance/adverse-action-notices.md --- # Adverse action notices > [!WARNING] > **This page is not legal advice.** It explains what Cascades' adverse-action node does and does not do so you can decide whether it satisfies your obligations. Notice content, timing and delivery requirements vary by use case and by state; consult your counsel and keep your templates under legal review. ## When is a notice required? Under FCRA §615(a), whenever you take an **adverse action** — deny a rental application, decline credit, refuse employment, change terms unfavourably — based in whole or in part on information in a consumer report, you must tell the consumer. The notice must include, at minimum: - that adverse action was taken, and the name, address and telephone number of the consumer reporting agency that supplied the report; - a statement that the agency did not make the decision and cannot explain why it was taken; - the consumer's right to obtain a free copy of the report from the agency within 60 days, and to dispute the accuracy or completeness of any information in it; - for credit decisions, the credit score used and the key factors that adversely affected it (the *risk-based pricing* / score-disclosure rules), where a score was used. For **employment**, §604(b)(3) adds a two-step sequence: a **pre-adverse action** notice with a copy of the report and the *Summary of Your Rights Under the FCRA*, a reasonable waiting period so the applicant can respond, and then the final **adverse action** notice. ## How do Cascades' flows map to that? | Flow | Sequence | What the node sends | |---|---|---| | `tenant_screening`, `credit_lending` | one notice (`notice_stage: final`) | The operator's notice template rendered to PDF with the selected reasons, emailed to the applicant; execution status is updated and `adverse_action.triggered` + `adverse_action.completed` fire together | | `employment` | `pre_adverse` → wait → `post_adverse` | The pre-adverse notice with the upstream report PDFs and the Summary of Rights attached; the execution waits (floored to **5 days**); then the final notice; `adverse_action.triggered` at the first send, `adverse_action.completed` at the second | The node's default `send_strategy` is **`draft_first`**: a decline parks an editable draft for a person to review and send. Unattended sending (`auto_send`) is a per-node opt-in. In human-review mode the reviewer's Deny dialog shows the pre-selected reasons and can edit them or skip the notice; a skip is recorded as `notice_intentionally_skipped` in the audit trail. Details: [Adverse action](/docs/concepts/adverse-action). ## Which reasons can a notice carry? Reasons are chosen from a fixed vocabulary, mapped from the decision conditions that failed (the mapping is editable by compliance without a deploy). Each has an English and a Spanish label. | Key | Label | |---|---| | `credit_score_low` | Credit score does not meet minimum requirements | | `insufficient_credit_history` | Insufficient credit history | | `delinquent_accounts` | Delinquent past or present credit obligations | | `too_many_inquiries` | Too many inquiries in the last 12 months | | `public_record` | Public record (bankruptcy, judgment, lien) | | `collections` | Collection accounts on credit report | | `high_debt_ratio` | Debt-to-income ratio is too high | | `insufficient_income` | Insufficient income for the obligation | | `employment_not_verified` | Unable to verify employment | | `identity_not_verified` | Unable to verify identity | | `residence_not_verified` | Unable to verify residence history | | `criminal_record` | Criminal record found | | `eviction_record` | Eviction record found | | `sex_offender_registry` | Sex offender registry match | | `terrorist_watchlist` | Terrorist watchlist match | | `fraud_alert` | Fraud alert or security freeze on credit file | | `incomplete_application` | Incomplete application | | `excessive_obligations` | Excessive obligations in relation to income | | `lack_of_references` | Lack of acceptable references | | `length_of_employment` | Length of employment insufficient | | `prior_lease_violations` | Prior lease violations or negative rental history | | `insufficient_assets` | Insufficient assets or reserves | | `other` | Other (see additional notes) | Account types on the notice: credit, mortgage, insurance, employment, tenant screening / rental, other. Action types: application denied, credit revoked or terms changed unfavourably, adverse employment action taken, other action taken. ## What is your responsibility? - **Template content.** Cascades renders *your* template. The agency identification, the rights statements, score disclosure and any state-specific language must be in it. Templates are versioned; every sent notice records the template version used. - **Recipient and delivery.** The node emails the applicant at the address in the payload. Delivery failures surface as `adverse_action.failed`; the execution and audit dashboard show the state. If your policy requires postal delivery, or the applicant has no email, you must handle that outside Cascades. - **Timing.** The employment waiting period is enforced at a minimum of five days; longer periods are configurable on the node. For other flows, the notice is sent when the decision is recorded. - **Individualised assessment and local rules.** Criminal-record and eviction decisions are subject to fair-housing and state and municipal rules (look-back limits, individualised assessment, ban-the-box). The cascade evaluates the rules you write; whether those rules are lawful where you operate is your call. - **Records.** Keep the audit trail (`GET /executions/{id}/review_events`), the notice artifacts (`GET /executions/{id}/notice_artifacts`) and your own application record together for the retention period your counsel advises. ## Related - [Adverse action](/docs/concepts/adverse-action) — the mechanics and webhook events - [FCRA permissible purpose](/docs/compliance/fcra-permissible-purpose) - [Webhook events](/docs/api/webhooks) --- --- title: API reference description: "Every Cascades REST endpoint, generated from the OpenAPI spec: workflows, executions, batches, webhooks." canonical_url: "https://cascades.crscreditapi.com/docs/api" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" --- # Cascades API reference Base URL: `https://cascades.crscreditapi.com/api/v1`. OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml ## Workflows - `GET /workflows` — [List workflows](https://cascades.crscreditapi.com/docs/api/workflows/list-workflows.md) - `POST /workflows` — [Create a workflow](https://cascades.crscreditapi.com/docs/api/workflows/create-workflow.md) - `POST /workflows/generate` — [Generate a workflow from a prompt](https://cascades.crscreditapi.com/docs/api/workflows/generate-workflow.md) - `GET /workflows/{id}` — [Show a workflow](https://cascades.crscreditapi.com/docs/api/workflows/get-workflow.md) - `PATCH /workflows/{id}` — [Update a workflow](https://cascades.crscreditapi.com/docs/api/workflows/update-workflow.md) - `DELETE /workflows/{id}` — [Delete a workflow](https://cascades.crscreditapi.com/docs/api/workflows/delete-workflow.md) - `GET /workflows/{id}/test_cases` — [List sandbox test cases for a workflow](https://cascades.crscreditapi.com/docs/api/workflows/list-workflow-test-cases.md) - `POST /workflows/{id}/trigger` — [Trigger a workflow](https://cascades.crscreditapi.com/docs/api/workflows/trigger-workflow.md) ## Workflow templates - `GET /workflow_templates` — [List published templates](https://cascades.crscreditapi.com/docs/api/workflow-templates/list-workflow-templates.md) - `GET /workflow_templates/{id}` — [Show a template (with definition)](https://cascades.crscreditapi.com/docs/api/workflow-templates/get-workflow-template.md) ## Executions - `GET /executions` — [List executions](https://cascades.crscreditapi.com/docs/api/executions/list-executions.md) - `GET /executions/{id}` — [Show an execution](https://cascades.crscreditapi.com/docs/api/executions/get-execution.md) - `POST /executions/{id}/cancel` — [Cancel an execution](https://cascades.crscreditapi.com/docs/api/executions/cancel-execution.md) - `POST /executions/{id}/re_evaluations` — [Re-evaluate an execution](https://cascades.crscreditapi.com/docs/api/executions/re-evaluate-execution.md) - `POST /executions/{id}/review_decisions` — [Approve or deny a paused review](https://cascades.crscreditapi.com/docs/api/executions/submit-review-decision.md) - `GET /executions/{id}/review_events` — [List review audit events](https://cascades.crscreditapi.com/docs/api/executions/list-review-events.md) - `POST /executions/{id}/review_assignment` — [Claim or unassign a review](https://cascades.crscreditapi.com/docs/api/executions/assign-review.md) - `POST /executions/{id}/review_notes` — [Append a review note](https://cascades.crscreditapi.com/docs/api/executions/add-review-note.md) - `POST /executions/review_bulk_actions` — [Bulk approve / deny / assign_to_me](https://cascades.crscreditapi.com/docs/api/executions/bulk-review-actions.md) - `GET /executions/{id}/report_artifacts` — [List credit-report PDFs for an execution](https://cascades.crscreditapi.com/docs/api/executions/list-report-artifacts.md) - `GET /executions/{id}/report_artifacts/{artifact_id}` — [Download a credit-report PDF](https://cascades.crscreditapi.com/docs/api/executions/get-report-artifact.md) - `GET /executions/{id}/notice_artifacts` — [List adverse-action notice PDFs for an execution](https://cascades.crscreditapi.com/docs/api/executions/list-notice-artifacts.md) - `GET /executions/{id}/notice_artifacts/{artifact_id}` — [Download an adverse-action notice PDF](https://cascades.crscreditapi.com/docs/api/executions/get-notice-artifact.md) ## Batches - `POST /workflows/{id}/batches` — [Create a batch from N rows](https://cascades.crscreditapi.com/docs/api/batches/create-batch.md) - `GET /batches` — [List batches](https://cascades.crscreditapi.com/docs/api/batches/list-batches.md) - `GET /batches/{id}` — [Show a batch](https://cascades.crscreditapi.com/docs/api/batches/get-batch.md) - `GET /batches/{id}/executions` — [List child executions of a batch](https://cascades.crscreditapi.com/docs/api/batches/list-batch-executions.md) - `POST /batches/{id}/cancel` — [Cancel a batch](https://cascades.crscreditapi.com/docs/api/batches/cancel-batch.md) - `POST /batches/{id}/resume` — [Resume an interrupted batch](https://cascades.crscreditapi.com/docs/api/batches/resume-batch.md) - `POST /batches/{id}/retry_failed` — [Retry failed and skipped rows](https://cascades.crscreditapi.com/docs/api/batches/retry-failed-batch.md) - `GET /batches/{id}/exports.csv` — [Download flat results CSV](https://cascades.crscreditapi.com/docs/api/batches/export-batch.md) - `GET /batches/{id}/exports.zip` — [Download structured ZIP of batch results](https://cascades.crscreditapi.com/docs/api/batches/export-batch-zip.md) - `GET /batches/{id}/adverse_action_notices.zip` — [Download all adverse-action notice PDFs as a ZIP](https://cascades.crscreditapi.com/docs/api/batches/download-batch-adverse-action-notices.md) ## Webhooks - `GET /webhook_subscriptions` — [List webhook subscriptions](https://cascades.crscreditapi.com/docs/api/webhooks/list-webhook-subscriptions.md) - `POST /webhook_subscriptions` — [Create a webhook subscription](https://cascades.crscreditapi.com/docs/api/webhooks/create-webhook-subscription.md) - `GET /webhook_subscriptions/{id}` — [Show a webhook subscription](https://cascades.crscreditapi.com/docs/api/webhooks/get-webhook-subscription.md) - `PATCH /webhook_subscriptions/{id}` — [Update a webhook subscription](https://cascades.crscreditapi.com/docs/api/webhooks/update-webhook-subscription.md) - `DELETE /webhook_subscriptions/{id}` — [Delete a webhook subscription](https://cascades.crscreditapi.com/docs/api/webhooks/delete-webhook-subscription.md) - `POST /webhook_subscriptions/{id}/rotate_secret` — [Rotate the signing secret](https://cascades.crscreditapi.com/docs/api/webhooks/rotate-webhook-secret.md) - `GET /webhook_deliveries` — [List webhook deliveries](https://cascades.crscreditapi.com/docs/api/webhooks/list-webhook-deliveries.md) - `GET /webhook_deliveries/{id}` — [Show a webhook delivery](https://cascades.crscreditapi.com/docs/api/webhooks/get-webhook-delivery.md) - `POST /webhook_deliveries/{id}/retry` — [Manually retry a failed or dead delivery](https://cascades.crscreditapi.com/docs/api/webhooks/retry-webhook-delivery.md) --- --- title: List workflows — GET /api/v1/workflows description: "Workflows owned by the account behind the token, newest first. Hidden single-report runner workflows are excluded. Requires workflows:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/list-workflows" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listWorkflows" --- # List workflows — GET /api/v1/workflows `GET https://cascades.crscreditapi.com/api/v1/workflows` - Operation id: `listWorkflows` - Scopes: `workflows:read` - Idempotent: no - MCP tool: `cascades_list_workflows` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listWorkflows`) Workflows owned by the account behind the token, newest first. Hidden single-report runner workflows are excluded. Requires `workflows:read`. ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | | `filter[active]` | boolean | no | Only active (true) or draft (false) workflows. | | `filter[use_case]` | string | no | Exact match, e.g. tenant_screening. | | `filter[trigger_type]` | string | no | Exact match. Not validated: a value outside the list returns an empty list, not an error. | ## Responses ### 200 — Workflow list `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of Workflow | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `code` | string, nullable | no | Short human-readable code assigned by Cascades. | |   `name` | string | yes | | |   `active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. | |   `trigger_type` | string | yes | One of: `"manual"`, `"event"`, `"scheduled"`. | |   `use_case` | string, nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 27, "object": "workflow", "code": "TSS-27", "name": "Tenant Screening Standard", "active": true, "trigger_type": "manual", "use_case": "tenant_screening", "created_at": "2026-08-01T10:00:00Z", "updated_at": "2026-09-01T09:30:00Z" } ], "meta": { "page": 1, "per_page": 25, "total_count": 1, "total_pages": 1, "object": "workflow", "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/workflows" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflows", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflows", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List workflows and summarise the result." # It will call the `cascades_list_workflows` tool with your key. ``` ## Related - [Cascades](https://cascades.crscreditapi.com/docs/concepts/cascades) - [Pagination and filtering](https://cascades.crscreditapi.com/docs/concepts/pagination-and-filtering) - [Quickstart](https://cascades.crscreditapi.com/docs/quickstart) --- --- 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 | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `code` | string, nullable | no | Short human-readable code assigned by Cascades. | |   `name` | string | yes | | |   `active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. | |   `trigger_type` | string | yes | One of: `"manual"`, `"event"`, `"scheduled"`. | |   `use_case` | string, nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | |   `note` | string | no | Present only when the account lacks production access and the cascade was therefore created as a draft. | | `meta` | Meta | no | | |   `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) --- --- title: Generate a workflow from a prompt — POST /api/v1/workflows/generate description: Builds a cascade definition from a plain-English policy description using the AI builder, validates it (with one automatic repair pass), and — by default… canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/generate-workflow" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#generateWorkflow" --- # Generate a workflow from a prompt — POST /api/v1/workflows/generate `POST https://cascades.crscreditapi.com/api/v1/workflows/generate` - Operation id: `generateWorkflow` - Scopes: `workflows:write` - Idempotent: no - MCP tool: `cascades_generate_workflow` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `generateWorkflow`) Builds a cascade definition from a plain-English policy description using the AI builder, validates it (with one automatic repair pass), and — by default — saves it as a new workflow exactly like `createWorkflow` (same draft rule for sandbox accounts, same `note`). Pass `create: false` to receive the generated definition as a preview without saving anything. Generation shares a small concurrency budget with the in-app designer; when every slot is busy the call returns `503 designer_busy` and should be retried after a short delay. Requires `workflows:write`. ## Request body `application/json` — `WorkflowGenerateRequest` | Field | Type | Required | Description | |---|---|---|---| | `prompt` | string | yes | Plain-English description of the decision policy to turn into a cascade (max 5 000 characters by default). | | `name` | string | no | Optional name override; otherwise the generator names the cascade. | | `use_case` | string | no | Optional use case label. Defaults to `tenant_screening`. | | `create` | boolean | no | `true` (default) saves the generated cascade and returns `201`; `false` returns the generated definition as a `200` preview without saving. | ```json { "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" } ``` ## Responses ### 201 — Generated and saved. `generation` describes the AI pass; `note` appears for sandbox accounts (draft). `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Workflow + SandboxDraftNote | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `code` | string, nullable | no | Short human-readable code assigned by Cascades. | |   `name` | string | yes | | |   `active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. | |   `trigger_type` | string | yes | One of: `"manual"`, `"event"`, `"scheduled"`. | |   `use_case` | string, nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | |   `generation` | CascadeGeneration | no | Metadata about the AI generation that produced a definition. | |     `name` | string | no | The name the generator chose. | |     `model` | string | no | The model that produced the definition. | |     `repaired` | boolean | no | `true` when the first draft failed validation and a repair pass fixed it. | |     `warnings` | array of CascadeGenerationWarning | no | Non-blocking findings from the definition validator (unreachable nodes, compliance hints). | |       `node_id` | string | yes | The `id` of the node the finding is about. | |       `code` | string | yes | Machine-readable finding. Current codes: `unreachable_node`, `missing_adverse_action`, `generic_adverse_action_reason`, `folded_thresholds`, `jurisdiction_review`. | |       `message` | string | yes | Human-readable explanation with the suggested fix. | |   `note` | string | no | Present only when the account lacks production access and the cascade was therefore created as a draft. | | `meta` | Meta | no | | |   `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": 42, "object": "workflow", "code": "FCS-42", "name": "FICO 650 Screening", "active": true, "trigger_type": "manual", "use_case": "tenant_screening", "created_at": "2026-09-09T14:10:00Z", "updated_at": "2026-09-09T14:10:00Z", "generation": { "name": "FICO 650 Screening", "model": "claude-opus-4-8", "repaired": false, "warnings": [] } }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 200 — Preview (`create: false`). Nothing was saved. `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | CascadeGenerationPreview | no | Returned by `generateWorkflow` when `create` is `false`. Nothing is saved. | |   `object` | string | no | One of: `"cascade_generation"`. | |   `definition` | array of CascadeNode | no | A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. | |     `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |     `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |     `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |     `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |     `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |     `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |       `store_result_as` | string | no | | |       `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |     `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |       `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |       `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |     `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |     `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |     `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |     `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |     `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |       `to` | string | no | | |       `subject` | string | no | | |       `body` | string | no | | |     `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |       `recipient` | string | no | | |       `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |       `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |       `reason` | string | no | | |     `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |     `wait` | object | no | `Wait` only. The delay before the next node runs. | |       `duration` | number | no | | |       `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |     `target_step` | string | no | `GoTo` only. The node id to jump to. | |   `valid` | boolean | no | Whether the definition passed the validator. | |   `name` | string | no | | |   `model` | string | no | | |   `repaired` | boolean | no | | |   `warnings` | array of CascadeGenerationWarning | no | | |     `node_id` | string | yes | The `id` of the node the finding is about. | |     `code` | string | yes | Machine-readable finding. Current codes: `unreachable_node`, `missing_adverse_action`, `generic_adverse_action_reason`, `folded_thresholds`, `jurisdiction_review`. | |     `message` | string | yes | Human-readable explanation with the suggested fix. | | `meta` | Meta | no | | |   `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": { "object": "cascade_generation", "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ], "valid": true, "name": "string", "model": "string", "repaired": true, "warnings": [ { "node_id": "step_fico_check", "code": "missing_adverse_action", "message": "Decision node \"step_fico_check\" has a fail branch that does not end in an AdverseAction. FCRA declines should send an adverse-action notice." } ] }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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` (blank or over-long `prompt`, or the saved workflow failed validation) or `generation_failed` (the model could not produce a valid definition; `details` lists the validator's findings). `application/json` — `ErrorEnvelope` ```json { "error": { "code": "generation_failed", "message": "The generated cascade did not pass validation.", "details": [ { "field": "definition", "message": "Decision node \"step_score_check\" uses operator \">=\", which is not valid." } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/generation_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" } } ``` ### 503 — `designer_busy` — every generation slot is in use. Retry after a few seconds. `application/json` — `ErrorEnvelope` ```json { "error": { "code": "designer_busy", "message": "The cascade designer is busy right now. Please try again in a moment.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/designer_busy" } } ``` ## Code samples ### cURL ```shell curl -X POST "https://cascades.crscreditapi.com/api/v1/workflows/generate" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/generate", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }) }); 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/generate", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }, ) 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/generate") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" } 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(`{ "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; otherwise send an adverse-action notice for review.", "use_case": "tenant_screening" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/workflows/generate", 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: # "Generate a workflow from a prompt and summarise the result." # It will call the `cascades_generate_workflow` tool with your key. ``` ## Related - [Generate a cascade with AI](https://cascades.crscreditapi.com/docs/guides/ai-generate-a-cascade) --- --- title: Show a workflow — GET /api/v1/workflows/{id} description: "Returns the workflow including its full definition. Requires workflows:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/get-workflow" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getWorkflow" --- # Show a workflow — GET /api/v1/workflows/{id} `GET https://cascades.crscreditapi.com/api/v1/workflows/{id}` - Operation id: `getWorkflow` - Scopes: `workflows:read` - Idempotent: no - MCP tool: `cascades_get_workflow` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getWorkflow`) Returns the workflow including its full `definition`. Requires `workflows:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns 404. | ## Responses ### 200 — Workflow with definition `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Workflow | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `code` | string, nullable | no | Short human-readable code assigned by Cascades. | |   `name` | string | yes | | |   `active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. | |   `trigger_type` | string | yes | One of: `"manual"`, `"event"`, `"scheduled"`. | |   `use_case` | string, nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | |   `definition` | array of CascadeNode | no | A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. | |     `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |     `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |     `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |     `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |     `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |     `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |       `store_result_as` | string | no | | |       `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |     `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |       `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |       `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |     `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |     `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |     `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |     `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |     `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |       `to` | string | no | | |       `subject` | string | no | | |       `body` | string | no | | |     `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |       `recipient` | string | no | | |       `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |       `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |       `reason` | string | no | | |     `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |     `wait` | object | no | `Wait` only. The delay before the next node runs. | |       `duration` | number | no | | |       `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |     `target_step` | string | no | `GoTo` only. The node id to jump to. | | `meta` | Meta | no | | |   `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": 27, "object": "workflow", "code": "TSS-27", "name": "Tenant Screening Standard", "active": true, "trigger_type": "manual", "use_case": "tenant_screening", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ] }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/workflows/27" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflows/27", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/27") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflows/27", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show a workflow 27 and summarise the result." # It will call the `cascades_get_workflow` tool with your key. ``` ## Related - [Cascades](https://cascades.crscreditapi.com/docs/concepts/cascades) --- --- title: Update a workflow — PATCH /api/v1/workflows/{id} description: "Partial update: only the keys present in the body change. To edit the graph, read it with getWorkflow, modify the array, and send the full definition back." canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/update-workflow" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#updateWorkflow" --- # Update a workflow — PATCH /api/v1/workflows/{id} `PATCH https://cascades.crscreditapi.com/api/v1/workflows/{id}` - Operation id: `updateWorkflow` - Scopes: `workflows:write` - Idempotent: no - MCP tool: `cascades_update_workflow` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `updateWorkflow`) Partial update: only the keys present in the body change. To edit the graph, read it with `getWorkflow`, modify the array, and send the full `definition` back. `PUT /workflows/{id}` is accepted as an alias with identical (partial) semantics. Setting `active: true` from an account without production access is refused with `403 activation_forbidden` rather than silently dropped. Requires `workflows:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns 404. | ## Request body `application/json` — `WorkflowUpdateRequest` | Field | Type | Required | Description | |---|---|---|---| | `name` | string | no | | | `definition` | CascadeDefinition | no | Replacement node graph (the full array, not a diff). Read the current one with `getWorkflow` first. | | `use_case` | string, nullable | no | | | `trigger_type` | string | no | Unknown values are ignored (the current value is kept). One of: `"manual"`, `"event"`, `"scheduled"`. | | `active` | boolean | no | `true` activates, `false` makes it a draft. Activating requires production access (`403 activation_forbidden` otherwise). | ```json { "name": "Tenant Screening — Premium" } ``` ## Responses ### 200 — Updated workflow with definition `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Workflow | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `code` | string, nullable | no | Short human-readable code assigned by Cascades. | |   `name` | string | yes | | |   `active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. | |   `trigger_type` | string | yes | One of: `"manual"`, `"event"`, `"scheduled"`. | |   `use_case` | string, nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | |   `definition` | array of CascadeNode | no | A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. | |     `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |     `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |     `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |     `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |     `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |     `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |       `store_result_as` | string | no | | |       `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |     `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |       `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |       `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |     `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |     `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |     `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |     `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |     `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |       `to` | string | no | | |       `subject` | string | no | | |       `body` | string | no | | |     `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |       `recipient` | string | no | | |       `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |       `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |       `reason` | string | no | | |     `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |     `wait` | object | no | `Wait` only. The delay before the next node runs. | |       `duration` | number | no | | |       `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |     `target_step` | string | no | `GoTo` only. The node id to jump to. | | `meta` | Meta | no | | |   `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": 27, "object": "workflow", "code": "TSS-27", "name": "Tenant Screening Standard", "active": true, "trigger_type": "manual", "use_case": "tenant_screening", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ] }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 400 — `malformed_body` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` ### 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 — `insufficient_scope` or `activation_forbidden` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "activation_forbidden", "message": "Activating a cascade requires production access. It stays a draft, which runs in test mode.", "details": [ { "field": "active", "message": "requires production access" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/activation_forbidden" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 422 — `validation_failed` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The workflow could not be updated.", "details": [ { "field": "definition", "message": "can only have one Applicant Source node" } ], "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 PATCH "https://cascades.crscreditapi.com/api/v1/workflows/27" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "name": "Tenant Screening — Premium" }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27", { method: "PATCH", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "name": "Tenant Screening — Premium" }) }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.patch( "https://cascades.crscreditapi.com/api/v1/workflows/27", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "name": "Tenant Screening — Premium" }, ) 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/27") request = Net::HTTP::Patch.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "name": "Tenant Screening — Premium" } 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 — Premium" }`) req, err := http.NewRequest("PATCH", "https://cascades.crscreditapi.com/api/v1/workflows/27", 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: # "Update a workflow 27 and summarise the result." # It will call the `cascades_update_workflow` tool with your key. ``` ## Related - [Cascades](https://cascades.crscreditapi.com/docs/concepts/cascades) --- --- title: Delete a workflow — DELETE /api/v1/workflows/{id} description: Soft-deletes the cascade. It disappears from listWorkflows, can no longer be triggered, and its id returns 404 from then on. canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/delete-workflow" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#deleteWorkflow" --- # Delete a workflow — DELETE /api/v1/workflows/{id} `DELETE https://cascades.crscreditapi.com/api/v1/workflows/{id}` - Operation id: `deleteWorkflow` - Scopes: `workflows:write` - Idempotent: no - MCP tool: `cascades_delete_workflow` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `deleteWorkflow`) Soft-deletes the cascade. It disappears from `listWorkflows`, can no longer be triggered, and its id returns `404` from then on. Existing executions and their artifacts are retained. Requires `workflows:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns 404. | ## Responses ### 204 — Deleted (no 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 DELETE "https://cascades.crscreditapi.com/api/v1/workflows/27" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27", { method: "DELETE", headers: { "Authorization": "Bearer {{API_KEY}}" } }); console.log(response.status); // 204 ``` ### Python ```python import requests response = requests.delete( "https://cascades.crscreditapi.com/api/v1/workflows/27", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) response.raise_for_status() print(response.status_code) # 204 ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://cascades.crscreditapi.com/api/v1/workflows/27") request = Net::HTTP::Delete.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end puts response.code # 204 ``` ### Go ```go package main import ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("DELETE", "https://cascades.crscreditapi.com/api/v1/workflows/27", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Delete a workflow 27 and summarise the result." # It will call the `cascades_delete_workflow` tool with your key. ``` ## Related - [Cascades](https://cascades.crscreditapi.com/docs/concepts/cascades) --- --- title: List sandbox test cases for a workflow — GET /api/v1/workflows/{id}/test_cases description: The synthetic subjects registered in the CRS Testcase Service for this cascade's product nodes (credit, criminal, eviction …). canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/list-workflow-test-cases" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listWorkflowTestCases" --- # List sandbox test cases for a workflow — GET /api/v1/workflows/{id}/test_cases `GET https://cascades.crscreditapi.com/api/v1/workflows/{id}/test_cases` - Operation id: `listWorkflowTestCases` - Scopes: `workflows:read` - Idempotent: no - MCP tool: `cascades_list_test_cases` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listWorkflowTestCases`) The synthetic subjects registered in the CRS Testcase Service for this cascade's product nodes (credit, criminal, eviction …). Each carries a ready-to-run `payload` shaped for `triggerWorkflow`, so a test-mode run needs no hand-written applicant data. A test-mode trigger for a subject registered for every product node in the cascade replays its authored bureau responses at no charge (`input_context.sandbox_replay` is `true`); a subject that covers only some of the products gets schema mocks like any other applicant (set `payload.scenario` to `fail_path` to exercise decline branches). Live-mode sandbox runs replay registered subjects too. A credit case's own criteria carry only name and SSN, so blank fields (`dob`, the `property` address) are filled from the same subject's other registry cases, such as its criminal or eviction case, whichever products the cascade uses. Without them CRS refuses the credit order. Only the cases authored for Cascades (`case_name` starts with `Cascades_`) are listed. The registry's other fixtures mostly lack the first name and address CRS needs, so their runs would fail at the credit step. Best-effort: an unreachable registry yields an empty list, never a 5xx. Results are cached for a few minutes. Requires `workflows:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns 404. | ## Responses ### 200 — Test-case list (not paginated) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | TestCaseList | no | | |   `object` | string | no | One of: `"test_case_list"`. | |   `workflow_id` | integer | no | | |   `test_cases` | array of TestCase | no | | |     `id` | integer | no | The subject id in the CRS Testcase registry. | |     `case_name` | string | no | | |     `product_code` | string | no | The billing product code this subject was registered for. | |     `payload` | object | no | Drop-in body for `triggerWorkflow` (`payload.applicant` and, when the subject carries one, `payload.property`). | |       `applicant` | ApplicantPayload | no | Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. | |       `property` | object | no | Property / tenancy details when the subject was registered with them. | | `meta` | Meta | no | | |   `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": { "object": "test_case_list", "workflow_id": 27, "test_cases": [ { "id": 73, "case_name": "Cascades_XProduct_Approve_EFX", "product_code": "EQF1003", "payload": { "applicant": { "first_name": "AVERY", "last_name": "CLARKSON", "middle_name": "", "name": "AVERY CLARKSON", "email": "avery.clarkson@example.com", "phone": "", "ssn": "666900001", "dob": "1982-01-01" }, "property": { "address": "1803 Norma", "unit": "", "city": "Cottonwood", "state": "CA", "zip": "91502", "full_address": "1803 Norma, Cottonwood, CA 91502" } } } ] }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/27/test_cases") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List sandbox test cases for a workflow 27 and summarise the result." # It will call the `cascades_list_test_cases` tool with your key. ``` ## Related - [Test identities](https://cascades.crscreditapi.com/docs/sandbox/test-identities) - [Your first screening](https://cascades.crscreditapi.com/docs/guides/first-screening) --- --- title: Trigger a workflow — POST /api/v1/workflows/{id}/trigger description: Starts an execution and returns 202 immediately; the run proceeds asynchronously. Poll getExecution until status is terminal (completed, failed,… canonical_url: "https://cascades.crscreditapi.com/docs/api/workflows/trigger-workflow" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#triggerWorkflow" --- # Trigger a workflow — POST /api/v1/workflows/{id}/trigger `POST https://cascades.crscreditapi.com/api/v1/workflows/{id}/trigger` - Operation id: `triggerWorkflow` - Scopes: `executions:write` - Idempotent: yes (`Idempotency-Key` header required) - Asynchronous: returns `202 Accepted`; poll `GET /executions/{id}` - MCP tool: `cascades_trigger_workflow` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `triggerWorkflow`) Starts an execution and returns `202` immediately; the run proceeds asynchronously. Poll `getExecution` until `status` is terminal (`completed`, `failed`, `cancelled`, `skipped`) or `awaiting_review`, or subscribe to `execution.completed` / `execution.failed`. **Test vs live.** `execution_mode` defaults to `test`, which uses synthetic vendor data and never bills — and works on drafts (`active: false`), so you can exercise a cascade before activating it. `live` requires an account with production access (`403 live_mode_forbidden` otherwise) **and** an active cascade (`409 workflow_inactive` otherwise). **Idempotency.** `Idempotency-Key` is required (`422 missing_idempotency_key` if absent). Race-safe under concurrent identical submissions: the loser receives the same execution as the winner. A reused key with a different body returns `409 idempotency_conflict`. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns 404. | ## Header parameters | Name | Type | Required | Description | |---|---|---|---| | `Idempotency-Key` | string | yes | Unique per-business-event key. Identical resubmissions return the original response; conflicting bodies return 409 idempotency_conflict. Concurrent identical requests are race-safe. A missing or blank header returns 422 missing_idempotency_key. The header is accepted up to 200 characters, but the stored key is namespaced to your client (api_client::, and for re-evaluations api_client::reeval::) and the stored value is capped at 200 characters — so the effective limit on trigger is about 185 characters and lower still on re_evaluations. Use a UUID (36 characters) or another key of at most 128 characters to stay clear of the cap. | ## Request body `application/json` — `TriggerRequest` | Field | Type | Required | Description | |---|---|---|---| | `payload` | object | no | The decision input. Put the subject under `applicant`; arbitrary nested fields are allowed and become the execution's `input_context`. Defaults to `{}` when omitted. | |   `applicant` | ApplicantPayload | no | Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. | |   `property` | object | no | Optional property / tenancy details (as returned by `listWorkflowTestCases`). | | `execution_mode` | string | no | `test` uses synthetic vendor data and never bills; `live` requires production access and an active cascade. One of: `"test"`, `"live"`. | | `external_reference` | string | no | Your correlation id for this run. Echoed on the execution and on every webhook event. | ```json { "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" } ``` ## Responses ### 202 — Execution accepted `application/json` — `TriggerResponse` | Field | Type | Required | Description | |---|---|---|---| | `data` | ExecutionSummary | no | The shape returned by `triggerWorkflow`. Compared with `Execution` it carries `execution_mode` and never `assigned_to_user_id`. | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `execution_key` | string, nullable | no | The namespaced idempotency key (`api_client::`). | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "pending", "execution_mode": "test", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": false, "review_status": "none", "started_at": null, "completed_at": null, "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` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` ### 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 — `insufficient_scope` or `live_mode_forbidden` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "live_mode_forbidden", "message": "Live mode requires production access.", "details": [ { "field": "execution_mode", "message": "live mode requires production access" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/live_mode_forbidden" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `idempotency_conflict` or `workflow_inactive` (live trigger on a draft) `application/json` — `ErrorEnvelope` ```json { "error": { "code": "idempotency_conflict", "message": "The Idempotency-Key has already been used with a different request payload.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/idempotency_conflict" } } ``` ### 422 — `missing_idempotency_key` or `validation_failed` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "missing_idempotency_key", "message": "Idempotency-Key header is required.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/missing_idempotency_key" } } ``` ### 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/27/trigger" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -d '{ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27/trigger", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, body: JSON.stringify({ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }) }); 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/27/trigger", headers={ "Authorization": "Bearer {{API_KEY}}", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6", }, json={ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }, ) 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/27/trigger") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request["Idempotency-Key"] = "3fa85f64-5717-4562-b3fc-2c963f66afa6" request.body = <<~JSON { "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" } 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(`{ "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" } }, "execution_mode": "test", "external_reference": "rv-app-20491" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/workflows/27/trigger", bytes.NewBuffer(body)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") req.Header.Set("Content-Type", "application/json") req.Header.Set("Idempotency-Key", "3fa85f64-5717-4562-b3fc-2c963f66afa6") 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: # "Trigger a workflow 27 and summarise the result." # It will call the `cascades_trigger_workflow` tool with your key. ``` ## Related - [Executions](https://cascades.crscreditapi.com/docs/concepts/executions) - [Idempotency](https://cascades.crscreditapi.com/docs/concepts/idempotency) - [Sandbox vs live](https://cascades.crscreditapi.com/docs/concepts/sandbox-vs-live) - [Your first screening](https://cascades.crscreditapi.com/docs/guides/first-screening) - [Quickstart](https://cascades.crscreditapi.com/docs/quickstart) --- --- title: List published templates — GET /api/v1/workflow_templates description: "Read-only catalog, ordered by name. Drafts and inactive templates are never exposed via the API. Requires workflow_templates:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/workflow-templates/list-workflow-templates" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listWorkflowTemplates" --- # List published templates — GET /api/v1/workflow_templates `GET https://cascades.crscreditapi.com/api/v1/workflow_templates` - Operation id: `listWorkflowTemplates` - Scopes: `workflow_templates:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listWorkflowTemplates`) Read-only catalog, ordered by name. Drafts and inactive templates are never exposed via the API. Requires `workflow_templates:read`. ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | | `filter[featured]` | boolean | no | | | `filter[category]` | string | no | | ## Responses ### 200 — Template list `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of WorkflowTemplate | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `name` | string | no | | |   `description` | string, nullable | no | | |   `icon` | string, nullable | no | | |   `color` | string, nullable | no | | |   `category` | string, nullable | no | | |   `active` | boolean | no | | |   `featured` | boolean | no | | |   `published_at` | string (date-time), nullable | no | | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 1, "object": "workflow_template", "name": "string", "description": "string", "icon": "string", "color": "string", "category": "string", "active": true, "featured": true, "published_at": "2026-05-08T18:41:05Z" } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } } ``` ### 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" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/workflow_templates" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflow_templates", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflow_templates", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/workflow_templates") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflow_templates", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List published templates using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /workflow_templates with curl. ``` ## Related - [Cascades](https://cascades.crscreditapi.com/docs/concepts/cascades) --- --- title: Show a template (with definition) — GET /api/v1/workflow_templates/{id} description: "Returns the template including its definition, which you can pass to createWorkflow. Requires workflow_templates:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/workflow-templates/get-workflow-template" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getWorkflowTemplate" --- # Show a template (with definition) — GET /api/v1/workflow_templates/{id} `GET https://cascades.crscreditapi.com/api/v1/workflow_templates/{id}` - Operation id: `getWorkflowTemplate` - Scopes: `workflow_templates:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getWorkflowTemplate`) Returns the template including its `definition`, which you can pass to `createWorkflow`. Requires `workflow_templates:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | | ## Responses ### 200 — Template detail `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | WorkflowTemplate | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `name` | string | no | | |   `description` | string, nullable | no | | |   `icon` | string, nullable | no | | |   `color` | string, nullable | no | | |   `category` | string, nullable | no | | |   `active` | boolean | no | | |   `featured` | boolean | no | | |   `published_at` | string (date-time), nullable | no | | |   `definition` | array of CascadeNode | no | A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. | |     `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |     `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |     `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |     `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |     `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |     `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |       `store_result_as` | string | no | | |       `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |     `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |       `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |       `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |         `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `condition_groups` | array of object | no | | |           `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |           `conditions` | array of CascadeCondition | yes | | |             `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |             `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |             `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |             `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |     `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |     `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |     `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |     `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |     `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |       `to` | string | no | | |       `subject` | string | no | | |       `body` | string | no | | |     `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |       `recipient` | string | no | | |       `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |       `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |       `reason` | string | no | | |     `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |     `wait` | object | no | `Wait` only. The delay before the next node runs. | |       `duration` | number | no | | |       `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |     `target_step` | string | no | `GoTo` only. The node id to jump to. | |   `source_workflow_id` | integer, nullable | no | | | `meta` | Meta | no | | |   `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": 1, "object": "workflow_template", "name": "string", "description": "string", "icon": "string", "color": "string", "category": "string", "active": true, "featured": true, "published_at": "2026-05-08T18:41:05Z", "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ], "source_workflow_id": 1 }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/workflow_templates/5" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflow_templates/5", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/workflow_templates/5", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/workflow_templates/5") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/workflow_templates/5", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show a template (with definition) 5 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /workflow_templates/{id} with curl. ``` ## Related - [Cascades](https://cascades.crscreditapi.com/docs/concepts/cascades) --- --- title: List executions — GET /api/v1/executions description: Executions started by the calling api_client, newest first. CSV filters (status, review_status) accept several comma-separated values; an unknown value… canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/list-executions" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listExecutions" --- # List executions — GET /api/v1/executions `GET https://cascades.crscreditapi.com/api/v1/executions` - Operation id: `listExecutions` - Scopes: `executions:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listExecutions`) Executions started by the calling api_client, newest first. CSV filters (`status`, `review_status`) accept several comma-separated values; an unknown value returns `422`. Requires `executions:read`. ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | | `filter[workflow_id]` | integer | no | | | `filter[status]` | string | no | CSV of queued, pending, running, waiting, awaiting_review, completed, failed, cancelled, skipped | | `filter[needs_human_review]` | boolean | no | | | `filter[review_status]` | string | no | CSV of none, in_review, approved, denied | | `filter[batch_execution_id]` | integer | no | | | `filter[execution_key]` | string | no | Exact match on the namespaced idempotency key. | | `filter[external_reference]` | string | no | | ## Responses ### 200 — Execution list `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of Execution | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_key` | string, nullable | no | | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } } ``` ### 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 — Request body or params failed validation `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "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 "https://cascades.crscreditapi.com/api/v1/executions" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List executions using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions with curl. ``` ## Related - [Executions](https://cascades.crscreditapi.com/docs/concepts/executions) - [Pagination and filtering](https://cascades.crscreditapi.com/docs/concepts/pagination-and-filtering) - [Handle human review](https://cascades.crscreditapi.com/docs/guides/handle-human-review) --- --- title: Show an execution — GET /api/v1/executions/{id} description: "Returns the detail view (Execution + input_context). Tokens with executions:read:full additionally receive step_outputs and failed_steps, which typically…" canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/get-execution" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getExecution" --- # Show an execution — GET /api/v1/executions/{id} `GET https://cascades.crscreditapi.com/api/v1/executions/{id}` - Operation id: `getExecution` - Scopes: `executions:read` - Idempotent: no - MCP tool: `cascades_get_execution` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getExecution`) Returns the detail view (`Execution` + `input_context`). Tokens with `executions:read:full` additionally receive `step_outputs` and `failed_steps`, which typically contain applicant PII and vendor responses. This is the endpoint to poll after `triggerWorkflow` and `reEvaluateExecution`. Requires `executions:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 200 — Execution detail (or full detail if scope allows) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | one of | no | | |   `ExecutionFull` | ExecutionDetail | no | | |     `id` | integer | yes | | |     `object` | string | yes | | |     `workflow_id` | integer | yes | | |     `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |     `execution_key` | string, nullable | no | | |     `external_reference` | string, nullable | no | | |     `needs_human_review` | boolean | yes | | |     `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |     `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |     `started_at` | string (date-time), nullable | no | | |     `completed_at` | string (date-time), nullable | no | | |     `created_at` | string (date-time) | yes | | |     `updated_at` | string (date-time), nullable | no | | |     `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | |     `step_outputs` | object | no | Per-node results keyed by node id. | |       `{key}` | object | no | | |     `failed_steps` | array of object | no | | |   `ExecutionDetail` | Execution | no | | |     `id` | integer | yes | | |     `object` | string | yes | | |     `workflow_id` | integer | yes | | |     `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |     `execution_key` | string, nullable | no | | |     `external_reference` | string, nullable | no | | |     `needs_human_review` | boolean | yes | | |     `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |     `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |     `started_at` | string (date-time), nullable | no | | |     `completed_at` | string (date-time), nullable | no | | |     `created_at` | string (date-time) | yes | | |     `updated_at` | string (date-time), nullable | no | | |     `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "completed", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": false, "review_status": "none", "assigned_to_user_id": null, "started_at": "2026-09-09T14:00:01Z", "completed_at": "2026-09-09T14:00:09Z", "created_at": "2026-09-09T14:00:00Z", "updated_at": "2026-09-09T14:00:09Z", "input_context": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com" }, "execution_mode": "test", "submitted_by_user_id": 5, "user_approved": false, "external_reference": "rv-app-20491" } }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/executions/1842" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show an execution 1842 and summarise the result." # It will call the `cascades_get_execution` tool with your key. ``` ## Related - [Executions](https://cascades.crscreditapi.com/docs/concepts/executions) - [Your first screening](https://cascades.crscreditapi.com/docs/guides/first-screening) - [Quickstart](https://cascades.crscreditapi.com/docs/quickstart) --- --- title: Cancel an execution — POST /api/v1/executions/{id}/cancel description: Cancels a pending, running, or waiting execution, any outstanding waits, and any unresolved adverse-action drafts. canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/cancel-execution" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#cancelExecution" --- # Cancel an execution — POST /api/v1/executions/{id}/cancel `POST https://cascades.crscreditapi.com/api/v1/executions/{id}/cancel` - Operation id: `cancelExecution` - Scopes: `executions:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `cancelExecution`) Cancels a `pending`, `running`, or `waiting` execution, any outstanding waits, and any unresolved adverse-action drafts. The engine notices between nodes and stops dispatching further work, so a node already in flight may finish. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 202 — Cancelled `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Execution | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_key` | string, nullable | no | | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | |   `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "input_context": {} }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `execution_not_cancellable` (already terminal or awaiting review) `application/json` — `ErrorEnvelope` ```json { "error": { "code": "execution_not_cancellable", "message": "Execution is already completed.", "details": [ { "field": "status", "message": "completed" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/execution_not_cancellable" } } ``` ### 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/executions/1842/cancel" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/cancel", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}" } }); 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/executions/1842/cancel", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842/cancel") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/cancel", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Cancel an execution 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/cancel with curl. ``` ## Related - [Executions](https://cascades.crscreditapi.com/docs/concepts/executions) --- --- title: Re-evaluate an execution — POST /api/v1/executions/{id}/re_evaluations description: Creates a new execution that re-runs the source's input_context against the source's snapshotted definition (NOT the workflow's current definition) and… canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/re-evaluate-execution" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#reEvaluateExecution" --- # Re-evaluate an execution — POST /api/v1/executions/{id}/re_evaluations `POST https://cascades.crscreditapi.com/api/v1/executions/{id}/re_evaluations` - Operation id: `reEvaluateExecution` - Scopes: `executions:write` - Idempotent: yes (`Idempotency-Key` header required) - Asynchronous: returns `202 Accepted`; poll `GET /executions/{id}` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `reEvaluateExecution`) Creates a new execution that re-runs the source's `input_context` against the source's snapshotted definition (NOT the workflow's current definition) and returns `202`; poll `getExecution` on the new id. The source must belong to an active workflow, and re-running a live-mode execution requires production access. `Idempotency-Key` is required and is namespaced per source execution, so the same key can be reused across different sources. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Header parameters | Name | Type | Required | Description | |---|---|---|---| | `Idempotency-Key` | string | yes | Unique per-business-event key. Identical resubmissions return the original response; conflicting bodies return 409 idempotency_conflict. Concurrent identical requests are race-safe. A missing or blank header returns 422 missing_idempotency_key. The header is accepted up to 200 characters, but the stored key is namespaced to your client (api_client::, and for re-evaluations api_client::reeval::) and the stored value is capped at 200 characters — so the effective limit on trigger is about 185 characters and lower still on re_evaluations. Use a UUID (36 characters) or another key of at most 128 characters to stay clear of the cap. | ## Responses ### 202 — Re-evaluation accepted (the NEW execution) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Execution | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_key` | string, nullable | no | | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | |   `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "input_context": {} }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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 — `insufficient_scope` or `live_mode_forbidden` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "live_mode_forbidden", "message": "Re-running a live-mode execution requires production access.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/live_mode_forbidden" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `workflow_inactive` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "workflow_inactive", "message": "Only active workflows can be re-evaluated through the public API.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/workflow_inactive" } } ``` ### 422 — `missing_idempotency_key` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "missing_idempotency_key", "message": "Idempotency-Key header is required.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/missing_idempotency_key" } } ``` ### 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/executions/1842/re_evaluations" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/re_evaluations", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6" } }); 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/executions/1842/re_evaluations", headers={ "Authorization": "Bearer {{API_KEY}}", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6", }, ) 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/executions/1842/re_evaluations") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Idempotency-Key"] = "3fa85f64-5717-4562-b3fc-2c963f66afa6" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/re_evaluations", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") req.Header.Set("Idempotency-Key", "3fa85f64-5717-4562-b3fc-2c963f66afa6") 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: # "Re-evaluate an execution 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/re_evaluations with curl. ``` ## Related - [Executions](https://cascades.crscreditapi.com/docs/concepts/executions) - [Idempotency](https://cascades.crscreditapi.com/docs/concepts/idempotency) --- --- title: Approve or deny a paused review — POST /api/v1/executions/{id}/review_decisions description: "Records the decision on an execution that is awaiting_review (or waiting) with needs_human_review: true, auto-claims it for the caller if unassigned, and…" canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/submit-review-decision" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#submitReviewDecision" --- # Approve or deny a paused review — POST /api/v1/executions/{id}/review_decisions `POST https://cascades.crscreditapi.com/api/v1/executions/{id}/review_decisions` - Operation id: `submitReviewDecision` - Scopes: `executions:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `submitReviewDecision`) Records the decision on an execution that is `awaiting_review` (or `waiting`) with `needs_human_review: true`, auto-claims it for the caller if unassigned, and resumes the chosen branch. Test-mode executions resume inline and return `200` with the final state; live-mode executions enqueue the resume and return `202` with `resume_enqueued: true`. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Request body `application/json` — `ReviewDecisionRequest` | Field | Type | Required | Description | |---|---|---|---| | `decision` | string | yes | One of: `"approved"`, `"denied"`. | | `note` | string | no | | ```json { "decision": "approved", "note": "Manual review confirmed; OFAC clean." } ``` ## Responses ### 200 — Review decided and downstream branch ran inline (test mode) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | ExecutionDetail | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_key` | string, nullable | no | | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | |   `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | |   `resume_enqueued` | boolean | no | | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "input_context": {}, "resume_enqueued": false }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 202 — Review decided and downstream branch enqueued (live mode) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | ExecutionDetail | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_key` | string, nullable | no | | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | |   `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | |   `resume_enqueued` | boolean | no | | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "input_context": {}, "resume_enqueued": true }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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 — `insufficient_scope`, `review_assigned_elsewhere` or `live_mode_forbidden` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "review_assigned_elsewhere", "message": "This review is assigned to another user.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/review_assigned_elsewhere" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `review_not_pending` or `review_already_decided` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "review_not_pending", "message": "Execution is not awaiting human review.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/review_not_pending" } } ``` ### 422 — `validation_failed` — `decision` must be `approved` or `denied` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "decision", "message": "must be approved or denied" } ], "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/executions/1842/review_decisions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }) }); 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/executions/1842/review_decisions", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }, ) 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/executions/1842/review_decisions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "decision": "approved", "note": "Manual review confirmed; OFAC clean." } 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(`{ "decision": "approved", "note": "Manual review confirmed; OFAC clean." }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_decisions", 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: # "Approve or deny a paused review 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/review_decisions with curl. ``` ## Related - [Human review](https://cascades.crscreditapi.com/docs/concepts/human-review) - [Your first screening](https://cascades.crscreditapi.com/docs/guides/first-screening) - [Handle human review](https://cascades.crscreditapi.com/docs/guides/handle-human-review) --- --- title: List review audit events — GET /api/v1/executions/{id}/review_events description: Returns flagged / assigned / unassigned / approved / denied / note_added / re_evaluated events, plus the adverse_action_drafted / adverse_action_sent /… canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/list-review-events" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listReviewEvents" --- # List review audit events — GET /api/v1/executions/{id}/review_events `GET https://cascades.crscreditapi.com/api/v1/executions/{id}/review_events` - Operation id: `listReviewEvents` - Scopes: `executions:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listReviewEvents`) Returns flagged / assigned / unassigned / approved / denied / note_added / re_evaluated events, plus the adverse_action_drafted / adverse_action_sent / adverse_action_skipped / adverse_action_replayed notice events, ordered by created_at. Requires `executions:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | ## Responses ### 200 — Audit trail `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of ReviewEvent | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `execution_id` | integer | no | | |   `event_type` | string | no | The review events plus the adverse-action notice trail: `adverse_action_drafted` (a notice draft was parked for review), `adverse_action_sent` (the draft was sent), `adverse_action_skipped` (the draft was discarded without sending) and `adverse_action_replayed` (a sent notice was resent from the app). One of: `"flagged"`, `"assigned"`, `"unassigned"`, `"approved"`, `"denied"`, `"note_added"`, `"re_evaluated"`, `"adverse_action_drafted"`, `"adverse_action_sent"`, `"adverse_action_skipped"`, `"adverse_action_replayed"`. | |   `actor_user_id` | integer, nullable | no | | |   `note` | string, nullable | no | | |   `metadata` | object | no | | |   `created_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 1, "object": "review_event", "execution_id": 1, "event_type": "flagged", "actor_user_id": 1, "note": "string", "metadata": {}, "created_at": "2026-05-08T18:41:05Z" } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/executions/1842/review_events" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_events", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842/review_events", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842/review_events") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_events", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List review audit events 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions/{id}/review_events with curl. ``` ## Related - [Human review](https://cascades.crscreditapi.com/docs/concepts/human-review) - [Handle human review](https://cascades.crscreditapi.com/docs/guides/handle-human-review) --- --- title: Claim or unassign a review — POST /api/v1/executions/{id}/review_assignment description: "assignee: \"me\" claims the review for the calling api_user; assignee: null releases it. An assigned / unassigned audit event is recorded when the assignee…" canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/assign-review" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#assignReview" --- # Claim or unassign a review — POST /api/v1/executions/{id}/review_assignment `POST https://cascades.crscreditapi.com/api/v1/executions/{id}/review_assignment` - Operation id: `assignReview` - Scopes: `executions:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `assignReview`) `assignee: "me"` claims the review for the calling api_user; `assignee: null` releases it. An `assigned` / `unassigned` audit event is recorded when the assignee actually changes. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Request body `application/json` — `ReviewAssignmentRequest` | Field | Type | Required | Description | |---|---|---|---| | `assignee` | string, nullable | no | `me` claims for the calling api_user; null unassigns. One of: `"me"`, `nil`. | | `note` | string | no | | ```json { "assignee": "me" } ``` ## Responses ### 200 — Updated assignment `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Execution | no | | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_key` | string, nullable | no | | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | |   `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "input_context": {} }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 422 — `validation_failed` — `assignee` must be `"me"` or `null` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "assignee", "message": "must be 'me' to claim or null to unassign" } ], "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/executions/1842/review_assignment" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "assignee": "me" }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_assignment", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "assignee": "me" }) }); 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/executions/1842/review_assignment", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "assignee": "me" }, ) 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/executions/1842/review_assignment") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "assignee": "me" } 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(`{ "assignee": "me" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_assignment", 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: # "Claim or unassign a review 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/review_assignment with curl. ``` ## Related - [Human review](https://cascades.crscreditapi.com/docs/concepts/human-review) - [Handle human review](https://cascades.crscreditapi.com/docs/guides/handle-human-review) --- --- title: Append a review note — POST /api/v1/executions/{id}/review_notes description: "Records a note_added audit event on the execution. Requires executions:write." canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/add-review-note" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#addReviewNote" --- # Append a review note — POST /api/v1/executions/{id}/review_notes `POST https://cascades.crscreditapi.com/api/v1/executions/{id}/review_notes` - Operation id: `addReviewNote` - Scopes: `executions:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `addReviewNote`) Records a `note_added` audit event on the execution. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Request body `application/json` — `ReviewNoteRequest` | Field | Type | Required | Description | |---|---|---|---| | `note` | string | yes | | ```json { "note": "Pulled credit report manually; matches OFAC cleanly." } ``` ## Responses ### 201 — Note recorded `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | ReviewEvent | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `execution_id` | integer | no | | |   `event_type` | string | no | The review events plus the adverse-action notice trail: `adverse_action_drafted` (a notice draft was parked for review), `adverse_action_sent` (the draft was sent), `adverse_action_skipped` (the draft was discarded without sending) and `adverse_action_replayed` (a sent notice was resent from the app). One of: `"flagged"`, `"assigned"`, `"unassigned"`, `"approved"`, `"denied"`, `"note_added"`, `"re_evaluated"`, `"adverse_action_drafted"`, `"adverse_action_sent"`, `"adverse_action_skipped"`, `"adverse_action_replayed"`. | |   `actor_user_id` | integer, nullable | no | | |   `note` | string, nullable | no | | |   `metadata` | object | no | | |   `created_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 1, "object": "review_event", "execution_id": 1, "event_type": "flagged", "actor_user_id": 1, "note": "string", "metadata": {}, "created_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 422 — `validation_failed` — blank `note` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "note", "message": "must not 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/executions/1842/review_notes" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "note": "Pulled credit report manually; matches OFAC cleanly." }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/review_notes", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "note": "Pulled credit report manually; matches OFAC cleanly." }) }); 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/executions/1842/review_notes", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "note": "Pulled credit report manually; matches OFAC cleanly." }, ) 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/executions/1842/review_notes") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "note": "Pulled credit report manually; matches OFAC cleanly." } 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(`{ "note": "Pulled credit report manually; matches OFAC cleanly." }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/1842/review_notes", 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: # "Append a review note 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/{id}/review_notes with curl. ``` ## Related - [Human review](https://cascades.crscreditapi.com/docs/concepts/human-review) - [Handle human review](https://cascades.crscreditapi.com/docs/guides/handle-human-review) --- --- title: Bulk approve / deny / assign_to_me — POST /api/v1/executions/review_bulk_actions description: Always asynchronous. Approve/deny enqueue the resume per execution (enqueued counts them). Cap of 25 ids per request. canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/bulk-review-actions" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#bulkReviewActions" --- # Bulk approve / deny / assign_to_me — POST /api/v1/executions/review_bulk_actions `POST https://cascades.crscreditapi.com/api/v1/executions/review_bulk_actions` - Operation id: `bulkReviewActions` - Scopes: `executions:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `bulkReviewActions`) Always asynchronous. Approve/deny enqueue the resume per execution (`enqueued` counts them). Cap of 25 ids per request. Ids that do not resolve in the caller's scope are reported as `not_found` in `skipped` so existence is not leaked. Requires `executions:write`. ## Request body `application/json` — `BulkReviewRequest` | Field | Type | Required | Description | |---|---|---|---| | `execution_ids` | array of integer | yes | | | `bulk_action` | string | yes | One of: `"approve"`, `"deny"`, `"assign_to_me"`. | | `note` | string | no | | ```json { "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." } ``` ## Responses ### 202 — Bulk action processed `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | BulkReviewResult | no | | |   `object` | string | no | | |   `applied` | integer | no | | |   `enqueued` | integer | no | | |   `skipped` | array of object | no | | |     `id` | integer | no | | |     `reason` | string | no | One of: `"not_awaiting_review"`, `"review_not_pending"`, `"assigned_elsewhere"`, `"live_mode_forbidden"`, `"already_assigned"`, `"not_found"`. | | `meta` | Meta | no | | |   `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": { "object": "bulk_review_result", "applied": 4, "enqueued": 4, "skipped": [ { "id": 1846, "reason": "not_found" } ] }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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` — unknown `bulk_action`, empty `execution_ids`, or more than 25 ids `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "Bulk action exceeds the per-request maximum.", "details": [ { "field": "execution_ids", "message": "max 25 ids per request" } ], "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/executions/review_bulk_actions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/review_bulk_actions", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }) }); 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/executions/review_bulk_actions", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }, ) 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/executions/review_bulk_actions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." } 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(`{ "execution_ids": [ 1842, 1843, 1844, 1845, 1846 ], "bulk_action": "approve", "note": "Manual review batch 2026-09-09 — OFAC and credit clean." }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/executions/review_bulk_actions", 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: # "Bulk approve / deny / assign_to_me using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /executions/review_bulk_actions with curl. ``` ## Related - [Human review](https://cascades.crscreditapi.com/docs/concepts/human-review) - [Handle human review](https://cascades.crscreditapi.com/docs/guides/handle-human-review) --- --- title: List credit-report PDFs for an execution — GET /api/v1/executions/{id}/report_artifacts description: Metadata only; the whole list is returned in one response (not paginated — page / per_page are ignored). canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/list-report-artifacts" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listReportArtifacts" --- # List credit-report PDFs for an execution — GET /api/v1/executions/{id}/report_artifacts `GET https://cascades.crscreditapi.com/api/v1/executions/{id}/report_artifacts` - Operation id: `listReportArtifacts` - Scopes: `executions:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listReportArtifacts`) Metadata only; the whole list is returned in one response (not paginated — `page` / `per_page` are ignored). Download each file with `getReportArtifact`, which needs `executions:read:full`. Requires `executions:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 200 — Artifact list `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of Artifact | no | | |   `id` | integer | no | Artifact id; pass it as `artifact_id` to the matching download endpoint. | |   `object` | string | no | One of: `"report_artifact"`, `"notice_artifact"`. | |   `execution_id` | integer | no | | |   `filename` | string | no | | |   `content_type` | string | no | | |   `byte_size` | integer | no | | |   `generated_at` | string (date-time), nullable | no | | |   `downloadable` | boolean | no | `false` when the record exists but its file is no longer stored. Downloading it then returns `404 not_found`. | | `meta` | Meta | no | Meta for collections that are NOT paginated (artifact lists). The whole collection is always returned in one response and the `page` / `per_page` query parameters are ignored. The pagination fields are still present for shape compatibility but hold constant values: `page` is always 1, `total_pages` is always 1, and `per_page` equals `total_count`. | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `object` | string | yes | | |   `total_count` | integer | yes | Number of items in `data`. | |   `page` | integer | no | Always 1. One of: `1`. | |   `per_page` | integer | no | Always equal to `total_count`. | |   `total_pages` | integer | no | Always 1. One of: `1`. | ```json { "data": [ { "id": 9911, "object": "report_artifact", "execution_id": 1842, "filename": "Equifax_Credit_Report.pdf", "content_type": "application/pdf", "byte_size": 184223, "generated_at": "2026-09-09T14:00:07Z" } ], "meta": { "object": "report_artifact", "total_count": 1, "page": 1, "per_page": 1, "total_pages": 1, "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842/report_artifacts") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List credit-report pdfs for an execution 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions/{id}/report_artifacts with curl. ``` ## Related - [Executions](https://cascades.crscreditapi.com/docs/concepts/executions) --- --- title: Download a credit-report PDF — GET /api/v1/executions/{id}/report_artifacts/{artifact_id} description: "Returns the binary PDF as an attachment. Requires executions:read:full because the contents are raw applicant data." canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/get-report-artifact" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getReportArtifact" --- # Download a credit-report PDF — GET /api/v1/executions/{id}/report_artifacts/{artifact_id} `GET https://cascades.crscreditapi.com/api/v1/executions/{id}/report_artifacts/{artifact_id}` - Operation id: `getReportArtifact` - Scopes: `executions:read:full` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getReportArtifact`) Returns the binary PDF as an attachment. Requires `executions:read:full` because the contents are raw applicant data. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | | `artifact_id` | integer | yes | Artifact id as returned by the matching list endpoint. | ## Responses ### 200 — Binary PDF `application/pdf` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts/3311" \ -H "Authorization: Bearer {{API_KEY}}" \ -o cascades-get-report-artifact.pdf ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts/3311", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const bytes = Buffer.from(await response.arrayBuffer()); console.log(response.status, bytes.length); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts/3311", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) response.raise_for_status() with open("cascades-get-report-artifact.pdf", "wb") as f: f.write(response.content) ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts/3311") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end File.binwrite("cascades-get-report-artifact.pdf", response.body) ``` ### Go ```go package main import ( "fmt" "io" "net/http" "os" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842/report_artifacts/3311", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() out, _ := io.ReadAll(resp.Body) os.WriteFile("cascades-get-report-artifact.pdf", out, 0o644) fmt.Println(resp.StatusCode) } ``` ### 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: # "Download a credit-report pdf 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions/{id}/report_artifacts/{artifact_id} with curl. ``` ## Related - [Executions](https://cascades.crscreditapi.com/docs/concepts/executions) --- --- title: List adverse-action notice PDFs for an execution — GET /api/v1/executions/{id}/notice_artifacts description: "Metadata only; not paginated (page / per_page are ignored). Download each file with getNoticeArtifact, which needs executions:read:full." canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/list-notice-artifacts" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listNoticeArtifacts" --- # List adverse-action notice PDFs for an execution — GET /api/v1/executions/{id}/notice_artifacts `GET https://cascades.crscreditapi.com/api/v1/executions/{id}/notice_artifacts` - Operation id: `listNoticeArtifacts` - Scopes: `executions:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listNoticeArtifacts`) Metadata only; not paginated (`page` / `per_page` are ignored). Download each file with `getNoticeArtifact`, which needs `executions:read:full`. Requires `executions:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 200 — Artifact list `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of Artifact | no | | |   `id` | integer | no | Artifact id; pass it as `artifact_id` to the matching download endpoint. | |   `object` | string | no | One of: `"report_artifact"`, `"notice_artifact"`. | |   `execution_id` | integer | no | | |   `filename` | string | no | | |   `content_type` | string | no | | |   `byte_size` | integer | no | | |   `generated_at` | string (date-time), nullable | no | | |   `downloadable` | boolean | no | `false` when the record exists but its file is no longer stored. Downloading it then returns `404 not_found`. | | `meta` | Meta | no | Meta for collections that are NOT paginated (artifact lists). The whole collection is always returned in one response and the `page` / `per_page` query parameters are ignored. The pagination fields are still present for shape compatibility but hold constant values: `page` is always 1, `total_pages` is always 1, and `per_page` equals `total_count`. | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `object` | string | yes | | |   `total_count` | integer | yes | Number of items in `data`. | |   `page` | integer | no | Always 1. One of: `1`. | |   `per_page` | integer | no | Always equal to `total_count`. | |   `total_pages` | integer | no | Always 1. One of: `1`. | ```json { "data": [ { "id": 1, "object": "report_artifact", "execution_id": 1, "filename": "Adverse_Action_Notice.pdf", "content_type": "application/pdf", "byte_size": 1, "generated_at": "2026-05-08T18:41:05Z", "downloadable": true } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "object": "report_artifact", "total_count": 2, "page": 1, "per_page": 1, "total_pages": 1 } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/executions/1842/notice_artifacts") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List adverse-action notice pdfs for an execution 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions/{id}/notice_artifacts with curl. ``` ## Related - [Adverse action](https://cascades.crscreditapi.com/docs/concepts/adverse-action) - [Your first screening](https://cascades.crscreditapi.com/docs/guides/first-screening) --- --- title: Download an adverse-action notice PDF — GET /api/v1/executions/{id}/notice_artifacts/{artifact_id} description: "Returns the binary PDF as an attachment. Requires executions:read:full." canonical_url: "https://cascades.crscreditapi.com/docs/api/executions/get-notice-artifact" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getNoticeArtifact" --- # Download an adverse-action notice PDF — GET /api/v1/executions/{id}/notice_artifacts/{artifact_id} `GET https://cascades.crscreditapi.com/api/v1/executions/{id}/notice_artifacts/{artifact_id}` - Operation id: `getNoticeArtifact` - Scopes: `executions:read:full` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getNoticeArtifact`) Returns the binary PDF as an attachment. Requires `executions:read:full`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Execution id. Scoped to the calling api_client; a foreign id returns 404. | | `artifact_id` | integer | yes | Artifact id as returned by the matching list endpoint. | ## Responses ### 200 — Binary PDF `application/pdf` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts/3311" \ -H "Authorization: Bearer {{API_KEY}}" \ -o cascades-get-notice-artifact.pdf ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts/3311", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const bytes = Buffer.from(await response.arrayBuffer()); console.log(response.status, bytes.length); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts/3311", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) response.raise_for_status() with open("cascades-get-notice-artifact.pdf", "wb") as f: f.write(response.content) ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts/3311") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end File.binwrite("cascades-get-notice-artifact.pdf", response.body) ``` ### Go ```go package main import ( "fmt" "io" "net/http" "os" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts/3311", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() out, _ := io.ReadAll(resp.Body) os.WriteFile("cascades-get-notice-artifact.pdf", out, 0o644) fmt.Println(resp.StatusCode) } ``` ### 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: # "Download an adverse-action notice pdf 1842 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /executions/{id}/notice_artifacts/{artifact_id} with curl. ``` ## Related - [Adverse action](https://cascades.crscreditapi.com/docs/concepts/adverse-action) --- --- title: Create a batch from N rows — POST /api/v1/workflows/{id}/batches description: Pre-creates the batch and N queued child executions in a single transaction and returns 202; the rows are then processed asynchronously. canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/create-batch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#createBatch" --- # Create a batch from N rows — POST /api/v1/workflows/{id}/batches `POST https://cascades.crscreditapi.com/api/v1/workflows/{id}/batches` - Operation id: `createBatch` - Scopes: `executions:write` - Idempotent: yes (`Idempotency-Key` header required) - Asynchronous: returns `202 Accepted`; poll `GET /batches/{id}` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `createBatch`) Pre-creates the batch and N `queued` child executions in a single transaction and returns `202`; the rows are then processed asynchronously. Poll `getBatch` for the counters and `listBatchExecutions` for per-row status. Cap of 500 rows per request; every row must be an object. `live` requires production access (`403 live_mode_forbidden`). Batches need an **active** cascade, in test mode too. On a draft (`active: false`) every batch create returns `409 workflow_inactive` before the rows, the mode or the `Idempotency-Key` are checked. A single test-mode `triggerWorkflow` on a draft still runs (`202`). A sandbox account (not approved for production) cannot activate a cascade, so it gets this 409 on every batch. To run a batch, an approved account activates the cascade in the app, or with `PATCH /workflows/{id}` and `active: true` from a key made at /api-keys (docs keys cannot activate). `Idempotency-Key` is required (`422 missing_idempotency_key`). The key is compared against the resolved `execution_mode`, `external_reference` and a SHA-256 digest of the rows, so reusing a key with different content returns `409 idempotency_conflict`. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns 404. | ## Header parameters | Name | Type | Required | Description | |---|---|---|---| | `Idempotency-Key` | string | yes | Unique per-business-event key. Identical resubmissions return the original response; conflicting bodies return 409 idempotency_conflict. Concurrent identical requests are race-safe. A missing or blank header returns 422 missing_idempotency_key. The header is accepted up to 200 characters, but the stored key is namespaced to your client (api_client::, and for re-evaluations api_client::reeval::) and the stored value is capped at 200 characters — so the effective limit on trigger is about 185 characters and lower still on re_evaluations. Use a UUID (36 characters) or another key of at most 128 characters to stay clear of the cap. | ## Request body `application/json` — `BatchCreateRequest` | Field | Type | Required | Description | |---|---|---|---| | `rows` | array of object | yes | One object per applicant. Every row must be an object. | |   `external_reference` | string | no | | |   `applicant` | ApplicantPayload | no | Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. | | `execution_mode` | string | no | One of: `"test"`, `"live"`. | | `external_reference` | string | no | | ```json { "rows": [ { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com" } }, { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park", "email": "ben@example.com" } } ], "execution_mode": "test", "external_reference": "rentvine-batch-001" } ``` ## Responses ### 202 — Batch accepted `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Batch | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `workflow_id` | integer | no | | |   `status` | string | no | `completed` means every row reached a terminal status, even when some rows failed (see `failed_count`). `failed` means the batch itself stopped on an unexpected error; it is not accepted by `filter[status]` on `listBatches`. One of: `"pending"`, `"running"`, `"interrupted"`, `"completed"`, `"cancelled"`, `"failed"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `external_reference` | string, nullable | no | | |   `total_count` | integer | no | | |   `completed_count` | integer | no | | |   `failed_count` | integer | no | | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 77, "object": "batch", "workflow_id": 27, "status": "pending", "execution_mode": "test", "external_reference": "rentvine-batch-001", "total_count": 2, "completed_count": 0, "failed_count": 0, "started_at": null, "completed_at": null, "created_at": "2026-09-09T14:20:00Z", "updated_at": "2026-09-09T14:20:00Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 400 — `malformed_body` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` ### 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 — `insufficient_scope` or `live_mode_forbidden` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "live_mode_forbidden", "message": "Live mode requires production access.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/live_mode_forbidden" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `workflow_inactive` (the cascade is a draft, in any mode) or `idempotency_conflict` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "workflow_inactive", "message": "Only active workflows can run batches through the public API.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/workflow_inactive" } } ``` ### 422 — `missing_idempotency_key` or `validation_failed` (empty `rows`, more than 500 rows, a non-object row, or a bad `execution_mode`) `application/json` — `ErrorEnvelope` ```json { "error": { "code": "missing_idempotency_key", "message": "Idempotency-Key header is required.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/missing_idempotency_key" } } ``` ### 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/27/batches" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3fa85f64-5717-4562-b3fc-2c963f66afa6" \ -d '{ "rows": [ { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com" } }, { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park", "email": "ben@example.com" } } ], "execution_mode": "test", "external_reference": "rentvine-batch-001" }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/workflows/27/batches", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6" }, body: JSON.stringify({ "rows": [ { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com" } }, { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park", "email": "ben@example.com" } } ], "execution_mode": "test", "external_reference": "rentvine-batch-001" }) }); 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/27/batches", headers={ "Authorization": "Bearer {{API_KEY}}", "Idempotency-Key": "3fa85f64-5717-4562-b3fc-2c963f66afa6", }, json={ "rows": [ { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com" } }, { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park", "email": "ben@example.com" } } ], "execution_mode": "test", "external_reference": "rentvine-batch-001" }, ) 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/27/batches") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request["Idempotency-Key"] = "3fa85f64-5717-4562-b3fc-2c963f66afa6" request.body = <<~JSON { "rows": [ { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com" } }, { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park", "email": "ben@example.com" } } ], "execution_mode": "test", "external_reference": "rentvine-batch-001" } 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(`{ "rows": [ { "external_reference": "row-1", "applicant": { "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com" } }, { "external_reference": "row-2", "applicant": { "first_name": "Ben", "last_name": "Park", "email": "ben@example.com" } } ], "execution_mode": "test", "external_reference": "rentvine-batch-001" }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/workflows/27/batches", bytes.NewBuffer(body)) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") req.Header.Set("Content-Type", "application/json") req.Header.Set("Idempotency-Key", "3fa85f64-5717-4562-b3fc-2c963f66afa6") 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 batch from n rows 27 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /workflows/{id}/batches with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) - [Idempotency](https://cascades.crscreditapi.com/docs/concepts/idempotency) - [Sandbox vs live](https://cascades.crscreditapi.com/docs/concepts/sandbox-vs-live) --- --- title: List batches — GET /api/v1/batches description: "Batches created by the calling api_client, newest first. Requires executions:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/list-batches" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listBatches" --- # List batches — GET /api/v1/batches `GET https://cascades.crscreditapi.com/api/v1/batches` - Operation id: `listBatches` - Scopes: `executions:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listBatches`) Batches created by the calling api_client, newest first. Requires `executions:read`. ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | | `filter[status]` | string | no | CSV of pending, running, interrupted, completed, cancelled | | `filter[workflow_id]` | integer | no | | | `filter[external_reference]` | string | no | | ## Responses ### 200 — Batch list `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of Batch | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `workflow_id` | integer | no | | |   `status` | string | no | `completed` means every row reached a terminal status, even when some rows failed (see `failed_count`). `failed` means the batch itself stopped on an unexpected error; it is not accepted by `filter[status]` on `listBatches`. One of: `"pending"`, `"running"`, `"interrupted"`, `"completed"`, `"cancelled"`, `"failed"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `external_reference` | string, nullable | no | | |   `total_count` | integer | no | | |   `completed_count` | integer | no | | |   `failed_count` | integer | no | | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 77, "object": "batch", "workflow_id": 1, "status": "pending", "execution_mode": "test", "external_reference": "rentvine-batch-001", "total_count": 1, "completed_count": 1, "failed_count": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } } ``` ### 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 — Request body or params failed validation `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "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 "https://cascades.crscreditapi.com/api/v1/batches" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/batches", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/batches") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/batches", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List batches using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /batches with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) - [Pagination and filtering](https://cascades.crscreditapi.com/docs/concepts/pagination-and-filtering) --- --- title: Show a batch — GET /api/v1/batches/{id} description: Batch status and counters. This is the endpoint to poll after createBatch, resumeBatch and retryFailedBatch; the batch is finished when status is… canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/get-batch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getBatch" --- # Show a batch — GET /api/v1/batches/{id} `GET https://cascades.crscreditapi.com/api/v1/batches/{id}` - Operation id: `getBatch` - Scopes: `executions:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getBatch`) Batch status and counters. This is the endpoint to poll after `createBatch`, `resumeBatch` and `retryFailedBatch`; the batch is finished when `status` is `completed` or `cancelled` (`interrupted` means it stopped early and can be resumed). Requires `executions:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 200 — Batch detail `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Batch | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `workflow_id` | integer | no | | |   `status` | string | no | `completed` means every row reached a terminal status, even when some rows failed (see `failed_count`). `failed` means the batch itself stopped on an unexpected error; it is not accepted by `filter[status]` on `listBatches`. One of: `"pending"`, `"running"`, `"interrupted"`, `"completed"`, `"cancelled"`, `"failed"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `external_reference` | string, nullable | no | | |   `total_count` | integer | no | | |   `completed_count` | integer | no | | |   `failed_count` | integer | no | | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 77, "object": "batch", "workflow_id": 1, "status": "pending", "execution_mode": "test", "external_reference": "rentvine-batch-001", "total_count": 1, "completed_count": 1, "failed_count": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/batches/77" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/batches/77", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/batches/77") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/batches/77", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show a batch 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /batches/{id} with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) --- --- title: List child executions of a batch — GET /api/v1/batches/{id}/executions description: "Child rows ordered by row_index. Tokens with executions:read:full additionally receive applicant_name and applicant_email. Requires executions:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/list-batch-executions" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listBatchExecutions" --- # List child executions of a batch — GET /api/v1/batches/{id}/executions `GET https://cascades.crscreditapi.com/api/v1/batches/{id}/executions` - Operation id: `listBatchExecutions` - Scopes: `executions:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listBatchExecutions`) Child rows ordered by `row_index`. Tokens with `executions:read:full` additionally receive `applicant_name` and `applicant_email`. Requires `executions:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | | `filter[status]` | string | no | CSV of execution statuses | ## Responses ### 200 — Child execution list (row_index ordered) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of BatchExecutionRow | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `batch_execution_id` | integer | no | | |   `workflow_id` | integer | no | | |   `row_index` | integer | no | 0-based position in the submitted `rows` array. | |   `status` | string | no | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `retry_count` | integer | no | | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | no | | |   `review_status` | string | no | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `applicant_name` | string, nullable | no | Only with `executions:read:full`. | |   `applicant_email` | string, nullable | no | Only with `executions:read:full`. | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 1, "object": "execution", "batch_execution_id": 1, "workflow_id": 1, "row_index": 1, "status": "queued", "retry_count": 1, "external_reference": "string", "needs_human_review": true, "review_status": "none", "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "applicant_name": "string", "applicant_email": "string" } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 422 — Request body or params failed validation `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "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 "https://cascades.crscreditapi.com/api/v1/batches/77/executions" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77/executions", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/batches/77/executions", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/batches/77/executions") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/batches/77/executions", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List child executions of a batch 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /batches/{id}/executions with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) --- --- title: Cancel a batch — POST /api/v1/batches/{id}/cancel description: "Halts dispatch and propagates cancelled to in-flight and queued child executions. Requires executions:write." canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/cancel-batch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#cancelBatch" --- # Cancel a batch — POST /api/v1/batches/{id}/cancel `POST https://cascades.crscreditapi.com/api/v1/batches/{id}/cancel` - Operation id: `cancelBatch` - Scopes: `executions:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `cancelBatch`) Halts dispatch and propagates `cancelled` to in-flight and queued child executions. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 202 — Cancelled `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Batch | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `workflow_id` | integer | no | | |   `status` | string | no | `completed` means every row reached a terminal status, even when some rows failed (see `failed_count`). `failed` means the batch itself stopped on an unexpected error; it is not accepted by `filter[status]` on `listBatches`. One of: `"pending"`, `"running"`, `"interrupted"`, `"completed"`, `"cancelled"`, `"failed"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `external_reference` | string, nullable | no | | |   `total_count` | integer | no | | |   `completed_count` | integer | no | | |   `failed_count` | integer | no | | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 77, "object": "batch", "workflow_id": 1, "status": "pending", "execution_mode": "test", "external_reference": "rentvine-batch-001", "total_count": 1, "completed_count": 1, "failed_count": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `batch_not_cancellable` (not `pending`, `running` or `interrupted`) `application/json` — `ErrorEnvelope` ```json { "error": { "code": "batch_not_cancellable", "message": "Batch is in status completed and cannot be cancelled.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/batch_not_cancellable" } } ``` ### 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/batches/77/cancel" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77/cancel", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}" } }); 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/batches/77/cancel", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/batches/77/cancel") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/batches/77/cancel", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Cancel a batch 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /batches/{id}/cancel with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) --- --- title: Resume an interrupted batch — POST /api/v1/batches/{id}/resume description: Re-queues remaining children and restarts processing; poll getBatch. Re-checks workflow_inactive and live_mode_forbidden at resume time. canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/resume-batch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#resumeBatch" --- # Resume an interrupted batch — POST /api/v1/batches/{id}/resume `POST https://cascades.crscreditapi.com/api/v1/batches/{id}/resume` - Operation id: `resumeBatch` - Scopes: `executions:write` - Idempotent: no - Asynchronous: returns `202 Accepted`; poll `GET /batches/{id}` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `resumeBatch`) Re-queues remaining children and restarts processing; poll `getBatch`. Re-checks `workflow_inactive` and `live_mode_forbidden` at resume time. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 202 — Resumed `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Batch | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `workflow_id` | integer | no | | |   `status` | string | no | `completed` means every row reached a terminal status, even when some rows failed (see `failed_count`). `failed` means the batch itself stopped on an unexpected error; it is not accepted by `filter[status]` on `listBatches`. One of: `"pending"`, `"running"`, `"interrupted"`, `"completed"`, `"cancelled"`, `"failed"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `external_reference` | string, nullable | no | | |   `total_count` | integer | no | | |   `completed_count` | integer | no | | |   `failed_count` | integer | no | | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 77, "object": "batch", "workflow_id": 1, "status": "pending", "execution_mode": "test", "external_reference": "rentvine-batch-001", "total_count": 1, "completed_count": 1, "failed_count": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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 — `insufficient_scope` or `live_mode_forbidden` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `batch_not_resumable` or `workflow_inactive` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "batch_not_resumable", "message": "Batch is in status running and cannot be resumed.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/batch_not_resumable" } } ``` ### 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/batches/77/resume" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77/resume", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}" } }); 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/batches/77/resume", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/batches/77/resume") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/batches/77/resume", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Resume an interrupted batch 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /batches/{id}/resume with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) --- --- title: Retry failed and skipped rows — POST /api/v1/batches/{id}/retry_failed description: Bumps retry_count on failed rows under the per-row cap, requeues skipped rows, clears their stale terminal state, and restarts processing; poll getBatch. canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/retry-failed-batch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#retryFailedBatch" --- # Retry failed and skipped rows — POST /api/v1/batches/{id}/retry_failed `POST https://cascades.crscreditapi.com/api/v1/batches/{id}/retry_failed` - Operation id: `retryFailedBatch` - Scopes: `executions:write` - Idempotent: no - Asynchronous: returns `202 Accepted`; poll `GET /batches/{id}` - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `retryFailedBatch`) Bumps `retry_count` on failed rows under the per-row cap, requeues skipped rows, clears their stale terminal state, and restarts processing; poll `getBatch`. Re-checks `workflow_inactive` and `live_mode_forbidden`. Requires `executions:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 202 — Retry started `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | Batch | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `workflow_id` | integer | no | | |   `status` | string | no | `completed` means every row reached a terminal status, even when some rows failed (see `failed_count`). `failed` means the batch itself stopped on an unexpected error; it is not accepted by `filter[status]` on `listBatches`. One of: `"pending"`, `"running"`, `"interrupted"`, `"completed"`, `"cancelled"`, `"failed"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `external_reference` | string, nullable | no | | |   `total_count` | integer | no | | |   `completed_count` | integer | no | | |   `failed_count` | integer | no | | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 77, "object": "batch", "workflow_id": 1, "status": "pending", "execution_mode": "test", "external_reference": "rentvine-batch-001", "total_count": 1, "completed_count": 1, "failed_count": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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 — `insufficient_scope` or `live_mode_forbidden` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `batch_not_retryable` or `workflow_inactive` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "batch_not_retryable", "message": "Batch is in status completed and has no retryable rows.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/batch_not_retryable" } } ``` ### 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/batches/77/retry_failed" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77/retry_failed", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}" } }); 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/batches/77/retry_failed", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/batches/77/retry_failed") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/batches/77/retry_failed", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Retry failed and skipped rows 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /batches/{id}/retry_failed with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) --- --- title: Download flat results CSV — GET /api/v1/batches/{id}/exports.csv description: One row per child execution by default. Pass ?include=all to include re-evaluation revisions. canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/export-batch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#exportBatch" --- # Download flat results CSV — GET /api/v1/batches/{id}/exports.csv `GET https://cascades.crscreditapi.com/api/v1/batches/{id}/exports.csv` - Operation id: `exportBatch` - Scopes: `executions:read:full` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `exportBatch`) One row per child execution by default. Pass `?include=all` to include re-evaluation revisions. The same route without an extension — `GET /batches/{id}/exports` — also returns CSV (it is the default format); `.zip` selects the structured archive (`exportBatchZip`). Requires `executions:read:full` because the CSV contains raw applicant data. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `include` | string | no | all includes every re-evaluation revision of each row. | ## Responses ### 200 — CSV `text/csv` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/batches/77/exports.csv" \ -H "Authorization: Bearer {{API_KEY}}" \ -o cascades-export-batch.csv ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77/exports.csv", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const bytes = Buffer.from(await response.arrayBuffer()); console.log(response.status, bytes.length); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/batches/77/exports.csv", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) response.raise_for_status() with open("cascades-export-batch.csv", "wb") as f: f.write(response.content) ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://cascades.crscreditapi.com/api/v1/batches/77/exports.csv") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end File.binwrite("cascades-export-batch.csv", response.body) ``` ### Go ```go package main import ( "fmt" "io" "net/http" "os" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/batches/77/exports.csv", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() out, _ := io.ReadAll(resp.Body) os.WriteFile("cascades-export-batch.csv", out, 0o644) fmt.Println(resp.StatusCode) } ``` ### 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: # "Download flat results csv 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /batches/{id}/exports.csv with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) --- --- title: Download structured ZIP of batch results — GET /api/v1/batches/{id}/exports.zip description: ZIP containing an error report, per-product CSVs, JSONs, and report PDFs. Pass ?include=all for revision history. canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/export-batch-zip" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#exportBatchZip" --- # Download structured ZIP of batch results — GET /api/v1/batches/{id}/exports.zip `GET https://cascades.crscreditapi.com/api/v1/batches/{id}/exports.zip` - Operation id: `exportBatchZip` - Scopes: `executions:read:full` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `exportBatchZip`) ZIP containing an error report, per-product CSVs, JSONs, and report PDFs. Pass `?include=all` for revision history. A PDF whose file is no longer stored is left out and named in `MissingFiles.txt`. Requires `executions:read:full`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `include` | string | no | all includes every re-evaluation revision of each row. | ## Responses ### 200 — ZIP `application/zip` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/batches/77/exports.zip" \ -H "Authorization: Bearer {{API_KEY}}" \ -o cascades-export-batch-zip.zip ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77/exports.zip", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const bytes = Buffer.from(await response.arrayBuffer()); console.log(response.status, bytes.length); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/batches/77/exports.zip", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) response.raise_for_status() with open("cascades-export-batch-zip.zip", "wb") as f: f.write(response.content) ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://cascades.crscreditapi.com/api/v1/batches/77/exports.zip") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end File.binwrite("cascades-export-batch-zip.zip", response.body) ``` ### Go ```go package main import ( "fmt" "io" "net/http" "os" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/batches/77/exports.zip", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() out, _ := io.ReadAll(resp.Body) os.WriteFile("cascades-export-batch-zip.zip", out, 0o644) fmt.Println(resp.StatusCode) } ``` ### 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: # "Download structured zip of batch results 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /batches/{id}/exports.zip with curl. ``` ## Related - [Batches](https://cascades.crscreditapi.com/docs/concepts/batches) --- --- title: Download all adverse-action notice PDFs as a ZIP — GET /api/v1/batches/{id}/adverse_action_notices.zip description: One entry per notice, prefixed with the applicant name. A notice whose file is no longer stored is left out and named in MissingFiles.txt. canonical_url: "https://cascades.crscreditapi.com/docs/api/batches/download-batch-adverse-action-notices" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#downloadBatchAdverseActionNotices" --- # Download all adverse-action notice PDFs as a ZIP — GET /api/v1/batches/{id}/adverse_action_notices.zip `GET https://cascades.crscreditapi.com/api/v1/batches/{id}/adverse_action_notices.zip` - Operation id: `downloadBatchAdverseActionNotices` - Scopes: `executions:read:full` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `downloadBatchAdverseActionNotices`) One entry per notice, prefixed with the applicant name. A notice whose file is no longer stored is left out and named in `MissingFiles.txt`. Returns `404 not_found` when no notices exist for the batch. Requires `executions:read:full`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Batch id. Scoped to the calling api_client; a foreign id returns 404. | ## Responses ### 200 — ZIP `application/zip` ### 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" } } ``` ### 404 — Batch not found, or no notices have been generated for it `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "No adverse-action notices have been generated for this batch.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/batches/77/adverse_action_notices.zip" \ -H "Authorization: Bearer {{API_KEY}}" \ -o cascades-download-batch-adverse-action-notices.zip ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/batches/77/adverse_action_notices.zip", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const bytes = Buffer.from(await response.arrayBuffer()); console.log(response.status, bytes.length); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/batches/77/adverse_action_notices.zip", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) response.raise_for_status() with open("cascades-download-batch-adverse-action-notices.zip", "wb") as f: f.write(response.content) ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://cascades.crscreditapi.com/api/v1/batches/77/adverse_action_notices.zip") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end File.binwrite("cascades-download-batch-adverse-action-notices.zip", response.body) ``` ### Go ```go package main import ( "fmt" "io" "net/http" "os" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/batches/77/adverse_action_notices.zip", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() out, _ := io.ReadAll(resp.Body) os.WriteFile("cascades-download-batch-adverse-action-notices.zip", out, 0o644) fmt.Println(resp.StatusCode) } ``` ### 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: # "Download all adverse-action notice pdfs as a zip 77 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /batches/{id}/adverse_action_notices.zip with curl. ``` ## Related - [Adverse action](https://cascades.crscreditapi.com/docs/concepts/adverse-action) --- --- title: List webhook subscriptions — GET /api/v1/webhook_subscriptions description: "Subscriptions belonging to the calling api_client, newest first. signing_secret is never included. Requires webhooks:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/list-webhook-subscriptions" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listWebhookSubscriptions" --- # List webhook subscriptions — GET /api/v1/webhook_subscriptions `GET https://cascades.crscreditapi.com/api/v1/webhook_subscriptions` - Operation id: `listWebhookSubscriptions` - Scopes: `webhooks:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listWebhookSubscriptions`) Subscriptions belonging to the calling api_client, newest first. `signing_secret` is never included. Requires `webhooks:read`. ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | ## Responses ### 200 — Subscription list (signing_secret never exposed here) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of WebhookSubscription | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `url` | string | no | | |   `event_filter` | array of WebhookEventName | no | Empty = subscribed to ALL events. | |   `active` | boolean | no | | |   `last_delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 1, "object": "webhook_subscription", "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed" ], "active": true, "last_delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } } ``` ### 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" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/webhook_subscriptions") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List webhook subscriptions using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /webhook_subscriptions with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) --- --- title: Create a webhook subscription — POST /api/v1/webhook_subscriptions description: Returns the signing_secret EXACTLY ONCE. Store it; subsequent reads never re-expose it (use rotateWebhookSecret if you lose it). canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/create-webhook-subscription" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#createWebhookSubscription" --- # Create a webhook subscription — POST /api/v1/webhook_subscriptions `POST https://cascades.crscreditapi.com/api/v1/webhook_subscriptions` - Operation id: `createWebhookSubscription` - Scopes: `webhooks:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `createWebhookSubscription`) Returns the `signing_secret` EXACTLY ONCE. Store it; subsequent reads never re-expose it (use `rotateWebhookSecret` if you lose it). The URL is validated against the SSRF guard (no private / loopback / cloud-metadata addresses, no blocked service ports). An empty or omitted `event_filter` subscribes to every event. Requires `webhooks:write`. ## Request body `application/json` — `WebhookSubscriptionCreateRequest` | Field | Type | Required | Description | |---|---|---|---| | `url` | string (uri) | yes | HTTPS endpoint. Must resolve to a public address (no private, loopback or cloud-metadata ranges). | | `event_filter` | array of WebhookEventName | no | Events to receive. Omit or pass `[]` for all events; unknown names return `422`. | | `active` | boolean | no | | ```json { "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] } ``` ## Responses ### 201 — Created (response includes signing_secret) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | WebhookSubscription | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `url` | string | no | | |   `event_filter` | array of WebhookEventName | no | Empty = subscribed to ALL events. | |   `active` | boolean | no | | |   `last_delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | |   `signing_secret` | string | no | | | `meta` | Meta | no | | |   `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": 9, "object": "webhook_subscription", "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ], "active": true, "last_delivered_at": null, "created_at": "2026-09-09T13:00:00Z", "updated_at": "2026-09-09T13:00:00Z", "signing_secret": "whsec_4f1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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` — bad URL, non-public host, or an unknown event name `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "event_filter", "message": "contains unknown event: execution.started" } ], "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/webhook_subscriptions" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }) }); 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/webhook_subscriptions", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }, ) 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/webhook_subscriptions") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] } 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(`{ "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed", "execution.failed", "adverse_action.completed" ] }`) req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions", 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 webhook subscription using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /webhook_subscriptions with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) --- --- title: Show a webhook subscription — GET /api/v1/webhook_subscriptions/{id} description: "Requires webhooks:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/get-webhook-subscription" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getWebhookSubscription" --- # Show a webhook subscription — GET /api/v1/webhook_subscriptions/{id} `GET https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/{id}` - Operation id: `getWebhookSubscription` - Scopes: `webhooks:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getWebhookSubscription`) Requires `webhooks:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Webhook subscription id. Scoped to the calling api_client. | ## Responses ### 200 — Subscription (no signing_secret) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | WebhookSubscription | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `url` | string | no | | |   `event_filter` | array of WebhookEventName | no | Empty = subscribed to ALL events. | |   `active` | boolean | no | | |   `last_delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 1, "object": "webhook_subscription", "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed" ], "active": true, "last_delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/webhook_subscriptions/12") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show a webhook subscription 12 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /webhook_subscriptions/{id} with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) --- --- title: Update a webhook subscription — PATCH /api/v1/webhook_subscriptions/{id} description: Partial update of url, event_filter and active; omitted keys keep their value. PUT /webhook_subscriptions/{id} is accepted as an alias with the same… canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/update-webhook-subscription" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#updateWebhookSubscription" --- # Update a webhook subscription — PATCH /api/v1/webhook_subscriptions/{id} `PATCH https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/{id}` - Operation id: `updateWebhookSubscription` - Scopes: `webhooks:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `updateWebhookSubscription`) Partial update of `url`, `event_filter` and `active`; omitted keys keep their value. `PUT /webhook_subscriptions/{id}` is accepted as an alias with the same partial semantics. The response never includes the `signing_secret`. Requires `webhooks:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Webhook subscription id. Scoped to the calling api_client. | ## Request body `application/json` — `WebhookSubscriptionUpdateRequest` | Field | Type | Required | Description | |---|---|---|---| | `url` | string (uri) | no | | | `event_filter` | array of WebhookEventName | no | | | `active` | boolean | no | | ```json { "event_filter": [ "adverse_action.completed" ] } ``` ## Responses ### 200 — Updated (no signing_secret) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | WebhookSubscription | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `url` | string | no | | |   `event_filter` | array of WebhookEventName | no | Empty = subscribed to ALL events. | |   `active` | boolean | no | | |   `last_delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 1, "object": "webhook_subscription", "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed" ], "active": true, "last_delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 422 — Request body or params failed validation `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "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 PATCH "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12" \ -H "Authorization: Bearer {{API_KEY}}" \ -H "Content-Type: application/json" \ -d '{ "event_filter": [ "adverse_action.completed" ] }' ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", { method: "PATCH", headers: { "Authorization": "Bearer {{API_KEY}}", "Content-Type": "application/json" }, body: JSON.stringify({ "event_filter": [ "adverse_action.completed" ] }) }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.patch( "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", headers={ "Authorization": "Bearer {{API_KEY}}", }, json={ "event_filter": [ "adverse_action.completed" ] }, ) 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/webhook_subscriptions/12") request = Net::HTTP::Patch.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" request["Content-Type"] = "application/json" request.body = <<~JSON { "event_filter": [ "adverse_action.completed" ] } 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(`{ "event_filter": [ "adverse_action.completed" ] }`) req, err := http.NewRequest("PATCH", "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", 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: # "Update a webhook subscription 12 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call PATCH /webhook_subscriptions/{id} with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) --- --- title: Delete a webhook subscription — DELETE /api/v1/webhook_subscriptions/{id} description: "Permanently deletes the subscription and its delivery log. Requires webhooks:write." canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/delete-webhook-subscription" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#deleteWebhookSubscription" --- # Delete a webhook subscription — DELETE /api/v1/webhook_subscriptions/{id} `DELETE https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/{id}` - Operation id: `deleteWebhookSubscription` - Scopes: `webhooks:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `deleteWebhookSubscription`) Permanently deletes the subscription and its delivery log. Requires `webhooks:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Webhook subscription id. Scoped to the calling api_client. | ## Responses ### 204 — Deleted (no 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 DELETE "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", { method: "DELETE", headers: { "Authorization": "Bearer {{API_KEY}}" } }); console.log(response.status); // 204 ``` ### Python ```python import requests response = requests.delete( "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) response.raise_for_status() print(response.status_code) # 204 ``` ### Ruby ```ruby require "net/http" require "json" uri = URI("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12") request = Net::HTTP::Delete.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http| http.request(request) end puts response.code # 204 ``` ### Go ```go package main import ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("DELETE", "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Delete a webhook subscription 12 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call DELETE /webhook_subscriptions/{id} with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) --- --- title: Rotate the signing secret — POST /api/v1/webhook_subscriptions/{id}/rotate_secret description: Generates a new signing_secret (returned once, in this response) and preserves the previous one for 24 hours so deliveries during your deploy window ship… canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/rotate-webhook-secret" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#rotateWebhookSecret" --- # Rotate the signing secret — POST /api/v1/webhook_subscriptions/{id}/rotate_secret `POST https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/{id}/rotate_secret` - Operation id: `rotateWebhookSecret` - Scopes: `webhooks:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `rotateWebhookSecret`) Generates a new `signing_secret` (returned once, in this response) and preserves the previous one for 24 hours so deliveries during your deploy window ship with both `v1=` (new) and `v0=` (old) signatures. After the grace window only `v1=` ships. Requires `webhooks:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Webhook subscription id. Scoped to the calling api_client. | ## Responses ### 200 — New signing_secret returned (one-time) `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | WebhookSubscription | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `url` | string | no | | |   `event_filter` | array of WebhookEventName | no | Empty = subscribed to ALL events. | |   `active` | boolean | no | | |   `last_delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | |   `signing_secret` | string | no | | | `meta` | Meta | no | | |   `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": 1, "object": "webhook_subscription", "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed" ], "active": true, "last_delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "signing_secret": "whsec_4f1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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/webhook_subscriptions/12/rotate_secret" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12/rotate_secret", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}" } }); 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/webhook_subscriptions/12/rotate_secret", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/webhook_subscriptions/12/rotate_secret") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/webhook_subscriptions/12/rotate_secret", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Rotate the signing secret 12 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /webhook_subscriptions/{id}/rotate_secret with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) - [Verify webhook signatures](https://cascades.crscreditapi.com/docs/guides/verify-webhooks) --- --- title: List webhook deliveries — GET /api/v1/webhook_deliveries description: Delivery attempts across all of the calling api_client's subscriptions, newest first. The event payload and the subscriber's response body are… canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/list-webhook-deliveries" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#listWebhookDeliveries" --- # List webhook deliveries — GET /api/v1/webhook_deliveries `GET https://cascades.crscreditapi.com/api/v1/webhook_deliveries` - Operation id: `listWebhookDeliveries` - Scopes: `webhooks:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `listWebhookDeliveries`) Delivery attempts across all of the calling api_client's subscriptions, newest first. The event payload and the subscriber's response body are deliberately omitted. CSV filters accept several comma-separated values; unknown values return `422`. Requires `webhooks:read`. ## Query parameters | Name | Type | Required | Description | |---|---|---|---| | `page` | integer | no | 1-based page number. | | `per_page` | integer | no | Items per page. Values above 100 are clamped to 100. | | `filter[webhook_subscription_id]` | integer | no | | | `filter[event_type]` | string | no | CSV of event names | | `filter[status]` | string | no | CSV of pending, retrying, succeeded, failed, dead | ## Responses ### 200 — Delivery list `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | array of WebhookDelivery | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `webhook_subscription_id` | integer | no | | |   `event_type` | string | no | Lifecycle events Cascades can deliver. See `x-webhooks` for each payload. One of: `"execution.completed"`, `"execution.failed"`, `"adverse_action.triggered"`, `"adverse_action.completed"`, `"adverse_action.failed"`, `"adverse_action.retry_scheduled"`. | |   `status` | string | no | One of: `"pending"`, `"retrying"`, `"succeeded"`, `"failed"`, `"dead"`. | |   `attempts` | integer | no | | |   `max_attempts` | integer | no | | |   `last_response_status` | integer, nullable | no | | |   `last_attempted_at` | string (date-time), nullable | no | | |   `next_retry_at` | string (date-time), nullable | no | | |   `delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | |   `page` | integer | yes | | |   `per_page` | integer | yes | | |   `total_count` | integer | yes | | |   `total_pages` | integer | yes | | |   `object` | string | yes | The object type of every item in `data`. | ```json { "data": [ { "id": 1, "object": "webhook_delivery", "webhook_subscription_id": 1, "event_type": "execution.completed", "status": "pending", "attempts": 1, "max_attempts": 6, "last_response_status": 1, "last_attempted_at": "2026-05-08T18:41:05Z", "next_retry_at": "2026-05-08T18:41:05Z", "delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ], "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } } ``` ### 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 — Request body or params failed validation `application/json` — `ErrorEnvelope` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "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 "https://cascades.crscreditapi.com/api/v1/webhook_deliveries" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_deliveries", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/webhook_deliveries", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/webhook_deliveries") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/webhook_deliveries", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "List webhook deliveries using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /webhook_deliveries with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) - [Pagination and filtering](https://cascades.crscreditapi.com/docs/concepts/pagination-and-filtering) --- --- title: Show a webhook delivery — GET /api/v1/webhook_deliveries/{id} description: "Requires webhooks:read." canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/get-webhook-delivery" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#getWebhookDelivery" --- # Show a webhook delivery — GET /api/v1/webhook_deliveries/{id} `GET https://cascades.crscreditapi.com/api/v1/webhook_deliveries/{id}` - Operation id: `getWebhookDelivery` - Scopes: `webhooks:read` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `getWebhookDelivery`) Requires `webhooks:read`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Webhook delivery id. Scoped to the calling api_client's subscriptions. | ## Responses ### 200 — Delivery metadata `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | WebhookDelivery | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `webhook_subscription_id` | integer | no | | |   `event_type` | string | no | Lifecycle events Cascades can deliver. See `x-webhooks` for each payload. One of: `"execution.completed"`, `"execution.failed"`, `"adverse_action.triggered"`, `"adverse_action.completed"`, `"adverse_action.failed"`, `"adverse_action.retry_scheduled"`. | |   `status` | string | no | One of: `"pending"`, `"retrying"`, `"succeeded"`, `"failed"`, `"dead"`. | |   `attempts` | integer | no | | |   `max_attempts` | integer | no | | |   `last_response_status` | integer, nullable | no | | |   `last_attempted_at` | string (date-time), nullable | no | | |   `next_retry_at` | string (date-time), nullable | no | | |   `delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 1, "object": "webhook_delivery", "webhook_subscription_id": 1, "event_type": "execution.completed", "status": "pending", "attempts": 1, "max_attempts": 6, "last_response_status": 1, "last_attempted_at": "2026-05-08T18:41:05Z", "next_retry_at": "2026-05-08T18:41:05Z", "delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 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 "https://cascades.crscreditapi.com/api/v1/webhook_deliveries/9081" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_deliveries/9081", { method: "GET", headers: { "Authorization": "Bearer {{API_KEY}}" } }); const { data, meta } = await response.json(); console.log(response.status, data, meta.request_id); ``` ### Python ```python import requests response = requests.get( "https://cascades.crscreditapi.com/api/v1/webhook_deliveries/9081", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/webhook_deliveries/9081") request = Net::HTTP::Get.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("GET", "https://cascades.crscreditapi.com/api/v1/webhook_deliveries/9081", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Show a webhook delivery 9081 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call GET /webhook_deliveries/{id} with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) --- --- title: Manually retry a failed or dead delivery — POST /api/v1/webhook_deliveries/{id}/retry description: Resets the delivery to pending and re-enqueues it immediately, outside the automatic retry schedule. canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/retry-webhook-delivery" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#retryWebhookDelivery" --- # Manually retry a failed or dead delivery — POST /api/v1/webhook_deliveries/{id}/retry `POST https://cascades.crscreditapi.com/api/v1/webhook_deliveries/{id}/retry` - Operation id: `retryWebhookDelivery` - Scopes: `webhooks:write` - Idempotent: no - OpenAPI: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml (operationId `retryWebhookDelivery`) Resets the delivery to `pending` and re-enqueues it immediately, outside the automatic retry schedule. Refuses to re-deliver a row that already succeeded or is currently in flight. Requires `webhooks:write`. ## Path parameters | Name | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | Webhook delivery id. Scoped to the calling api_client's subscriptions. | ## Responses ### 202 — Retry enqueued `application/json` | Field | Type | Required | Description | |---|---|---|---| | `data` | WebhookDelivery | no | | |   `id` | integer | no | | |   `object` | string | no | | |   `webhook_subscription_id` | integer | no | | |   `event_type` | string | no | Lifecycle events Cascades can deliver. See `x-webhooks` for each payload. One of: `"execution.completed"`, `"execution.failed"`, `"adverse_action.triggered"`, `"adverse_action.completed"`, `"adverse_action.failed"`, `"adverse_action.retry_scheduled"`. | |   `status` | string | no | One of: `"pending"`, `"retrying"`, `"succeeded"`, `"failed"`, `"dead"`. | |   `attempts` | integer | no | | |   `max_attempts` | integer | no | | |   `last_response_status` | integer, nullable | no | | |   `last_attempted_at` | string (date-time), nullable | no | | |   `next_retry_at` | string (date-time), nullable | no | | |   `delivered_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | no | | |   `updated_at` | string (date-time) | no | | | `meta` | Meta | no | | |   `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": 1, "object": "webhook_delivery", "webhook_subscription_id": 1, "event_type": "execution.completed", "status": "pending", "attempts": 1, "max_attempts": 6, "last_response_status": 1, "last_attempted_at": "2026-05-08T18:41:05Z", "next_retry_at": "2026-05-08T18:41:05Z", "delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` ### 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" } } ``` ### 404 — Resource does not exist or belongs to another tenant / api_client `application/json` — `ErrorEnvelope` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` ### 409 — `delivery_not_retryable` `application/json` — `ErrorEnvelope` ```json { "error": { "code": "delivery_not_retryable", "message": "Delivery is in status succeeded and cannot be retried.", "details": [], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/delivery_not_retryable" } } ``` ### 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/webhook_deliveries/9081/retry" \ -H "Authorization: Bearer {{API_KEY}}" ``` ### Node.js ```javascript const response = await fetch("https://cascades.crscreditapi.com/api/v1/webhook_deliveries/9081/retry", { method: "POST", headers: { "Authorization": "Bearer {{API_KEY}}" } }); 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/webhook_deliveries/9081/retry", headers={ "Authorization": "Bearer {{API_KEY}}", }, ) 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/webhook_deliveries/9081/retry") request = Net::HTTP::Post.new(uri) request["Authorization"] = "Bearer {{API_KEY}}" 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 ( "fmt" "io" "net/http" ) func main() { req, err := http.NewRequest("POST", "https://cascades.crscreditapi.com/api/v1/webhook_deliveries/9081/retry", nil) if err != nil { panic(err) } req.Header.Set("Authorization", "Bearer {{API_KEY}}") 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: # "Manually retry a failed or dead delivery 9081 using the Cascades API." # No MCP tool covers this endpoint yet; the assistant can call POST /webhook_deliveries/{id}/retry with curl. ``` ## Related - [Webhooks](https://cascades.crscreditapi.com/docs/concepts/webhooks) --- --- title: Meta schema description: Fields of the Meta object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/Meta" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/Meta" --- # Meta Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/Meta) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | ```json { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } ``` --- --- title: PaginatedMeta schema description: Fields of the PaginatedMeta object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/PaginatedMeta" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/PaginatedMeta" --- # PaginatedMeta Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/PaginatedMeta) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | | `page` | integer | yes | | | `per_page` | integer | yes | | | `total_count` | integer | yes | | | `total_pages` | integer | yes | | | `object` | string | yes | The object type of every item in `data`. | ```json { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "page": 1, "per_page": 25, "total_count": 142, "total_pages": 6, "object": "execution" } ``` --- --- title: StaticCollectionMeta schema description: Meta for collections that are NOT paginated (artifact lists). The whole collection is always returned in one response and the page / per_page query… canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/StaticCollectionMeta" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/StaticCollectionMeta" --- # StaticCollectionMeta Meta for collections that are NOT paginated (artifact lists). The whole collection is always returned in one response and the `page` / `per_page` query parameters are ignored. The pagination fields are still present for shape compatibility but hold constant values: `page` is always 1, `total_pages` is always 1, and `per_page` equals `total_count`. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/StaticCollectionMeta) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `request_id` | string | yes | Correlation id for this request. Also sent as the `X-Request-Id` response header; quote it when contacting support. | | `object` | string | yes | | | `total_count` | integer | yes | Number of items in `data`. | | `page` | integer | no | Always 1. One of: `1`. | | `per_page` | integer | no | Always equal to `total_count`. | | `total_pages` | integer | no | Always 1. One of: `1`. | ```json { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "object": "report_artifact", "total_count": 2, "page": 1, "per_page": 1, "total_pages": 1 } ``` --- --- title: ErrorEnvelope schema description: The envelope returned by every non-2xx response, including 429 and the /api/* 404 catch-all. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ErrorEnvelope" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ErrorEnvelope" --- # ErrorEnvelope The envelope returned by every non-2xx response, including `429` and the `/api/*` 404 catch-all. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `error` | object | yes | | |   `code` | string | yes | Stable machine-readable error code. Branch on this, never on `message`. | |   `message` | string | yes | | |   `details` | array of object | no | Field-level detail when available; otherwise an empty array. | |     `field` | string | no | | |     `message` | string | no | | |   `request_id` | string | yes | | |   `doc_url` | string (uri) | no | Link to the reference page for this error code | ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` --- --- title: CascadeDefinition schema description: "A cascade's node graph: an ordered array of CascadeNode objects. Exactly one ApplicantSource node is the entry point; every other node is reached through…" canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/CascadeDefinition" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/CascadeDefinition" --- # CascadeDefinition A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/CascadeDefinition) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `[]` | CascadeNode | no | One node of a cascade. `id` and `type` are always required; the remaining required keys depend on `type` (see `x-required-keys-by-type`): \| type \| required keys \| purpose \| \|---\|---\|---\| \| `ApplicantSource` \| — \| Entry point; exposes the trigger payload as `context.applicant`. Exactly one per cascade. \| \| `BillingProduct` (alias `Product`) \| `billing_product_code` \| Pulls a report (credit, criminal, eviction, identity, income). \| \| `IfElse` (alias `Condition`) \| `branches` \| Evaluates conditions and routes to `pass_steps`, `human_review_steps` or `fail_steps`. \| \| `Wait` \| `wait` \| Pauses for `wait.duration` × `wait.unit`. \| \| `GoTo` \| `target_step` \| Runtime jump to another node (not a graph edge). \| \| `ShowResults` \| — \| Terminal node that surfaces collected outputs. \| \| `Action` \| `action_type` \| `Approve`, `SendEmail` (reads `email`), `AdverseAction` (reads `adverse_action`), `InternalNotification` (reads `internal_notification`). \| `Product` and `Condition` are legacy aliases the engine still resolves; new definitions should use `BillingProduct` and `IfElse`. Unknown keys are tolerated and ignored by the engine. | |   `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |   `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |   `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |   `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |   `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |   `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |     `store_result_as` | string | no | | |     `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |   `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |     `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |     `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |   `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |   `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |   `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |   `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |   `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |     `to` | string | no | | |     `subject` | string | no | | |     `body` | string | no | | |   `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |     `recipient` | string | no | | |     `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |     `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |     `reason` | string | no | | |   `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |   `wait` | object | no | `Wait` only. The delay before the next node runs. | |     `duration` | number | no | | |     `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |   `target_step` | string | no | `GoTo` only. The node id to jump to. | ```json [ { "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ] ``` --- --- title: CascadeNode schema description: "One node of a cascade. id and type are always required; the remaining required keys depend on type (see x-required-keys-by-type): | type | required keys |…" canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/CascadeNode" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/CascadeNode" --- # CascadeNode One node of a cascade. `id` and `type` are always required; the remaining required keys depend on `type` (see `x-required-keys-by-type`): | type | required keys | purpose | |---|---|---| | `ApplicantSource` | — | Entry point; exposes the trigger payload as `context.applicant`. Exactly one per cascade. | | `BillingProduct` (alias `Product`) | `billing_product_code` | Pulls a report (credit, criminal, eviction, identity, income). | | `IfElse` (alias `Condition`) | `branches` | Evaluates conditions and routes to `pass_steps`, `human_review_steps` or `fail_steps`. | | `Wait` | `wait` | Pauses for `wait.duration` × `wait.unit`. | | `GoTo` | `target_step` | Runtime jump to another node (not a graph edge). | | `ShowResults` | — | Terminal node that surfaces collected outputs. | | `Action` | `action_type` | `Approve`, `SendEmail` (reads `email`), `AdverseAction` (reads `adverse_action`), `InternalNotification` (reads `internal_notification`). | `Product` and `Condition` are legacy aliases the engine still resolves; new definitions should use `BillingProduct` and `IfElse`. Unknown keys are tolerated and ignored by the engine. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/CascadeNode) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | | `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | | `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | | `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | | `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | | `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |   `store_result_as` | string | no | | |   `billing_product_code` | string | no | Alternative location for `billing_product_code`. | | `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |   `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |     `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |     `condition_groups` | array of object | no | | |       `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `conditions` | array of CascadeCondition | yes | | |         `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |         `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |         `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |         `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |           `option 1` | string, nullable | no | | |           `option 2` | number | no | | |           `option 3` | boolean | no | | |   `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |     `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |     `condition_groups` | array of object | no | | |       `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `conditions` | array of CascadeCondition | yes | | |         `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |         `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |         `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |         `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |           `option 1` | string, nullable | no | | |           `option 2` | number | no | | |           `option 3` | boolean | no | | | `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | | `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | | `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | | `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | | `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |   `to` | string | no | | |   `subject` | string | no | | |   `body` | string | no | | | `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |   `recipient` | string | no | | |   `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |   `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |   `reason` | string | no | | | `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | | `wait` | object | no | `Wait` only. The delay before the next node runs. | |   `duration` | number | no | | |   `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | | `target_step` | string | no | `GoTo` only. The node id to jump to. | ```json { "id": "step_credit", "type": "ApplicantSource", "name": "Credit report", "next_step": [ "string" ], "billing_product_code": "EQF1003", "config": { "store_result_as": "string", "billing_product_code": "string" }, "branches": { "pass": { "group_logic": "and", "condition_groups": [ { "logic": "and", "conditions": [ { "source_node_id": "step_credit", "field": "creditScore.fico9", "operator": null, "value": 650 } ] } ] }, "human_review": { "group_logic": "and", "condition_groups": [ { "logic": "and", "conditions": [ { "source_node_id": "step_credit", "field": "creditScore.fico9", "operator": null, "value": 650 } ] } ] } }, "pass_steps": [ "string" ], "human_review_steps": [ "string" ], "fail_steps": [ "string" ], "action_type": "Approve", "email": { "to": "string", "subject": "string", "body": "string" }, "adverse_action": { "recipient": "{{applicant.email}}", "action_taken": "denied", "send_strategy": "draft_first", "reason": "string" }, "internal_notification": {}, "wait": { "duration": 24, "unit": "hours" }, "target_step": "string" } ``` --- --- title: CascadeBranch schema description: A branch of an IfElse node — one or more condition groups combined with group_logic. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/CascadeBranch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/CascadeBranch" --- # CascadeBranch A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/CascadeBranch) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | | `condition_groups` | array of object | no | | |   `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |   `conditions` | array of CascadeCondition | yes | | |     `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |     `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |     `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |     `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |       `option 1` | string, nullable | no | | |       `option 2` | number | no | | |       `option 3` | boolean | no | | ```json { "group_logic": "and", "condition_groups": [ { "logic": "and", "conditions": [ { "source_node_id": "step_credit", "field": "creditScore.fico9", "operator": "equals", "value": 650 } ] } ] } ``` --- --- title: CascadeCondition schema description: A single comparison against a product node's output (or a built-in context variable such as applicant.email). canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/CascadeCondition" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/CascadeCondition" --- # CascadeCondition A single comparison against a product node's output (or a built-in context variable such as `applicant.email`). Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/CascadeCondition) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `source_node_id` | string | yes | The `id` of the product node whose output is compared. | | `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | | `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | | `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |   `option 1` | string, nullable | no | | |   `option 2` | number | no | | |   `option 3` | boolean | no | | ```json { "source_node_id": "step_credit", "field": "creditScore.fico9", "operator": "equals", "value": 650 } ``` --- --- title: Workflow schema description: Fields of the Workflow object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/Workflow" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/Workflow" --- # Workflow Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/Workflow) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | | | `object` | string | yes | | | `code` | string, nullable | no | Short human-readable code assigned by Cascades. | | `name` | string | yes | | | `active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. | | `trigger_type` | string | yes | One of: `"manual"`, `"event"`, `"scheduled"`. | | `use_case` | string, nullable | no | | | `created_at` | string (date-time) | no | | | `updated_at` | string (date-time) | no | | ```json { "id": 27, "object": "workflow", "code": "TSS-27", "name": "Tenant Screening Standard", "active": true, "trigger_type": "manual", "use_case": "tenant_screening", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ``` --- --- title: WorkflowDetail schema description: Fields of the WorkflowDetail object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WorkflowDetail" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WorkflowDetail" --- # WorkflowDetail Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WorkflowDetail) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | | | `object` | string | yes | | | `code` | string, nullable | no | Short human-readable code assigned by Cascades. | | `name` | string | yes | | | `active` | boolean | yes | `true` = activated and triggerable in live mode. Drafts (`false`) can still be triggered in test mode. | | `trigger_type` | string | yes | One of: `"manual"`, `"event"`, `"scheduled"`. | | `use_case` | string, nullable | no | | | `created_at` | string (date-time) | no | | | `updated_at` | string (date-time) | no | | | `definition` | array of CascadeNode | no | A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. | |   `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |   `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |   `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |   `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |   `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |   `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |     `store_result_as` | string | no | | |     `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |   `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |     `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |     `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |   `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |   `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |   `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |   `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |   `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |     `to` | string | no | | |     `subject` | string | no | | |     `body` | string | no | | |   `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |     `recipient` | string | no | | |     `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |     `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |     `reason` | string | no | | |   `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |   `wait` | object | no | `Wait` only. The delay before the next node runs. | |     `duration` | number | no | | |     `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |   `target_step` | string | no | `GoTo` only. The node id to jump to. | ```json { "id": 27, "object": "workflow", "code": "TSS-27", "name": "Tenant Screening Standard", "active": true, "trigger_type": "manual", "use_case": "tenant_screening", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ] } ``` --- --- title: WorkflowCreateRequest schema description: Fields of the WorkflowCreateRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WorkflowCreateRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WorkflowCreateRequest" --- # WorkflowCreateRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WorkflowCreateRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | 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", "definition": "value", "use_case": "tenant_screening", "trigger_type": "manual", "active": true } ``` --- --- title: WorkflowUpdateRequest schema description: PATCH semantics — only the keys present in the body change; omitted keys keep their current value. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WorkflowUpdateRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WorkflowUpdateRequest" --- # WorkflowUpdateRequest PATCH semantics — only the keys present in the body change; omitted keys keep their current value. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WorkflowUpdateRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `name` | string | no | | | `definition` | CascadeDefinition | no | Replacement node graph (the full array, not a diff). Read the current one with `getWorkflow` first. | | `use_case` | string, nullable | no | | | `trigger_type` | string | no | Unknown values are ignored (the current value is kept). One of: `"manual"`, `"event"`, `"scheduled"`. | | `active` | boolean | no | `true` activates, `false` makes it a draft. Activating requires production access (`403 activation_forbidden` otherwise). | ```json { "name": "string", "definition": "value", "use_case": "string", "trigger_type": "manual", "active": true } ``` --- --- title: WorkflowGenerateRequest schema description: Fields of the WorkflowGenerateRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WorkflowGenerateRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WorkflowGenerateRequest" --- # WorkflowGenerateRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WorkflowGenerateRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `prompt` | string | yes | Plain-English description of the decision policy to turn into a cascade (max 5 000 characters by default). | | `name` | string | no | Optional name override; otherwise the generator names the cascade. | | `use_case` | string | no | Optional use case label. Defaults to `tenant_screening`. | | `create` | boolean | no | `true` (default) saves the generated cascade and returns `201`; `false` returns the generated definition as a `200` preview without saving. | ```json { "prompt": "Pull an Equifax credit report. Approve if FICO is at least 650; send an adverse-action notice otherwise.", "name": "string", "use_case": "string", "create": true } ``` --- --- title: CascadeGeneration schema description: Metadata about the AI generation that produced a definition. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/CascadeGeneration" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/CascadeGeneration" --- # CascadeGeneration Metadata about the AI generation that produced a definition. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/CascadeGeneration) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `name` | string | no | The name the generator chose. | | `model` | string | no | The model that produced the definition. | | `repaired` | boolean | no | `true` when the first draft failed validation and a repair pass fixed it. | | `warnings` | array of CascadeGenerationWarning | no | Non-blocking findings from the definition validator (unreachable nodes, compliance hints). | |   `node_id` | string | yes | The `id` of the node the finding is about. | |   `code` | string | yes | Machine-readable finding. Current codes: `unreachable_node`, `missing_adverse_action`, `generic_adverse_action_reason`, `folded_thresholds`, `jurisdiction_review`. | |   `message` | string | yes | Human-readable explanation with the suggested fix. | ```json { "name": "string", "model": "claude-opus-4-8", "repaired": true, "warnings": [ { "node_id": "step_fico_check", "code": "missing_adverse_action", "message": "Decision node \"step_fico_check\" has a fail branch that does not end in an AdverseAction. FCRA declines should send an adverse-action notice." } ] } ``` --- --- title: CascadeGenerationWarning schema description: One non-blocking finding from the definition validator, addressed to the node it concerns. The cascade is still valid and runnable. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/CascadeGenerationWarning" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/CascadeGenerationWarning" --- # CascadeGenerationWarning One non-blocking finding from the definition validator, addressed to the node it concerns. The cascade is still valid and runnable. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/CascadeGenerationWarning) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `node_id` | string | yes | The `id` of the node the finding is about. | | `code` | string | yes | Machine-readable finding. Current codes: `unreachable_node`, `missing_adverse_action`, `generic_adverse_action_reason`, `folded_thresholds`, `jurisdiction_review`. | | `message` | string | yes | Human-readable explanation with the suggested fix. | ```json { "node_id": "step_fico_check", "code": "missing_adverse_action", "message": "Decision node \"step_fico_check\" has a fail branch that does not end in an AdverseAction. FCRA declines should send an adverse-action notice." } ``` --- --- title: CascadeGenerationPreview schema description: Returned by generateWorkflow when create is false. Nothing is saved. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/CascadeGenerationPreview" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/CascadeGenerationPreview" --- # CascadeGenerationPreview Returned by `generateWorkflow` when `create` is `false`. Nothing is saved. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/CascadeGenerationPreview) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `object` | string | no | One of: `"cascade_generation"`. | | `definition` | array of CascadeNode | no | A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. | |   `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |   `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |   `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |   `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |   `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |   `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |     `store_result_as` | string | no | | |     `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |   `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |     `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |     `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |   `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |   `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |   `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |   `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |   `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |     `to` | string | no | | |     `subject` | string | no | | |     `body` | string | no | | |   `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |     `recipient` | string | no | | |     `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |     `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |     `reason` | string | no | | |   `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |   `wait` | object | no | `Wait` only. The delay before the next node runs. | |     `duration` | number | no | | |     `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |   `target_step` | string | no | `GoTo` only. The node id to jump to. | | `valid` | boolean | no | Whether the definition passed the validator. | | `name` | string | no | | | `model` | string | no | | | `repaired` | boolean | no | | | `warnings` | array of CascadeGenerationWarning | no | | |   `node_id` | string | yes | The `id` of the node the finding is about. | |   `code` | string | yes | Machine-readable finding. Current codes: `unreachable_node`, `missing_adverse_action`, `generic_adverse_action_reason`, `folded_thresholds`, `jurisdiction_review`. | |   `message` | string | yes | Human-readable explanation with the suggested fix. | ```json { "object": "cascade_generation", "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ], "valid": true, "name": "string", "model": "string", "repaired": true, "warnings": [ { "node_id": "step_fico_check", "code": "missing_adverse_action", "message": "Decision node \"step_fico_check\" has a fail branch that does not end in an AdverseAction. FCRA declines should send an adverse-action notice." } ] } ``` --- --- title: SandboxDraftNote schema description: Fields of the SandboxDraftNote object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/SandboxDraftNote" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/SandboxDraftNote" --- # SandboxDraftNote Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/SandboxDraftNote) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `note` | string | no | Present only when the account lacks production access and the cascade was therefore created as a draft. | ```json { "note": "Created as a draft: activating a cascade requires production access. Drafts run in test mode; live runs need an activated account." } ``` --- --- title: TestCase schema description: A registered sandbox subject for one of the cascade's products, with a payload ready to send to triggerWorkflow. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/TestCase" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/TestCase" --- # TestCase A registered sandbox subject for one of the cascade's products, with a payload ready to send to `triggerWorkflow`. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/TestCase) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | The subject id in the CRS Testcase registry. | | `case_name` | string | no | | | `product_code` | string | no | The billing product code this subject was registered for. | | `payload` | object | no | Drop-in body for `triggerWorkflow` (`payload.applicant` and, when the subject carries one, `payload.property`). | |   `applicant` | ApplicantPayload | no | Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. | |   `property` | object | no | Property / tenancy details when the subject was registered with them. | ```json { "id": 54, "case_name": "Cascades_Credit_660_Inq0", "product_code": "TUC1001", "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com", "ssn": "123-45-6789", "dob": "1989-04-12" }, "property": {} } } ``` --- --- title: TestCaseList schema description: Fields of the TestCaseList object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/TestCaseList" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/TestCaseList" --- # TestCaseList Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/TestCaseList) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `object` | string | no | One of: `"test_case_list"`. | | `workflow_id` | integer | no | | | `test_cases` | array of TestCase | no | | |   `id` | integer | no | The subject id in the CRS Testcase registry. | |   `case_name` | string | no | | |   `product_code` | string | no | The billing product code this subject was registered for. | |   `payload` | object | no | Drop-in body for `triggerWorkflow` (`payload.applicant` and, when the subject carries one, `payload.property`). | |     `applicant` | ApplicantPayload | no | Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. | |     `property` | object | no | Property / tenancy details when the subject was registered with them. | ```json { "object": "test_case_list", "workflow_id": 27, "test_cases": [ { "id": 54, "case_name": "Cascades_Credit_660_Inq0", "product_code": "TUC1001", "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com", "ssn": "123-45-6789", "dob": "1989-04-12" }, "property": {} } } ] } ``` --- --- title: WorkflowTemplate schema description: Fields of the WorkflowTemplate object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WorkflowTemplate" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WorkflowTemplate" --- # WorkflowTemplate Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WorkflowTemplate) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `name` | string | no | | | `description` | string, nullable | no | | | `icon` | string, nullable | no | | | `color` | string, nullable | no | | | `category` | string, nullable | no | | | `active` | boolean | no | | | `featured` | boolean | no | | | `published_at` | string (date-time), nullable | no | | ```json { "id": 1, "object": "workflow_template", "name": "string", "description": "string", "icon": "string", "color": "string", "category": "string", "active": true, "featured": true, "published_at": "2026-05-08T18:41:05Z" } ``` --- --- title: WorkflowTemplateDetail schema description: Fields of the WorkflowTemplateDetail object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WorkflowTemplateDetail" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WorkflowTemplateDetail" --- # WorkflowTemplateDetail Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WorkflowTemplateDetail) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `name` | string | no | | | `description` | string, nullable | no | | | `icon` | string, nullable | no | | | `color` | string, nullable | no | | | `category` | string, nullable | no | | | `active` | boolean | no | | | `featured` | boolean | no | | | `published_at` | string (date-time), nullable | no | | | `definition` | array of CascadeNode | no | A cascade's node graph: an ordered array of `CascadeNode` objects. Exactly one `ApplicantSource` node is the entry point; every other node is reached through the edge keys (`next_step`, `pass_steps`, `human_review_steps`, `fail_steps`), each of which is an array of node `id`s that must resolve within the same array. An empty array is accepted on create/update (an empty draft) but cannot be run. | |   `id` | string | yes | Unique within the definition, e.g. `step_credit`. Edge keys reference nodes by this value. | |   `type` | string | yes | One of: `"ApplicantSource"`, `"BillingProduct"`, `"Product"`, `"IfElse"`, `"Condition"`, `"Wait"`, `"GoTo"`, `"ShowResults"`, `"Action"`. | |   `name` | string | no | Human-readable label shown in the builder and in `step_outputs`. | |   `next_step` | array of string | no | Default forward edge — ids of the node(s) to run next. | |   `billing_product_code` | string | no | `BillingProduct` / `Product` only. The product to pull, e.g. `EQF1003` (Equifax credit), `TUC1001` (TransUnion credit), `CIC4002` (criminal), `CIC4003` (eviction). May also be nested as `config.billing_product_code`. | |   `config` | object | no | Optional per-node configuration. `store_result_as` names the key under which a product's output is stored. | |     `store_result_as` | string | no | | |     `billing_product_code` | string | no | Alternative location for `billing_product_code`. | |   `branches` | object | no | `IfElse` / `Condition` only. Conditions for the `pass` branch and (optionally) the `human_review` branch; anything that matches neither takes `fail_steps`. | |     `pass` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |     `human_review` | CascadeBranch | no | A branch of an `IfElse` node — one or more condition groups combined with `group_logic`. | |       `group_logic` | string | no | How the condition groups combine. Defaults to `and`. One of: `"and"`, `"or"`. | |       `condition_groups` | array of object | no | | |         `logic` | string | no | How the conditions inside this group combine. Defaults to `and`. One of: `"and"`, `"or"`. | |         `conditions` | array of CascadeCondition | yes | | |           `source_node_id` | string | yes | The `id` of the product node whose output is compared. | |           `field` | string | yes | Exact output field path from that product's schema, e.g. `creditScore.fico9`. | |           `operator` | string | yes | One of: `"equals"`, `"not_equals"`, `"greater_than"`, `"greater_than_or_equal"`, `"less_than"`, `"less_than_or_equal"`, `"contains"`, `"not_contains"`, `"starts_with"`, `"ends_with"`, `"is_empty"`, `"is_not_empty"`, `"is_true"`, `"is_false"`. | |           `value` | one of | no | The comparison value. Required for every operator except the unary ones (`is_empty`, `is_not_empty`, `is_true`, `is_false`), which accept `null` or no `value` at all. | |             `option 1` | string, nullable | no | | |             `option 2` | number | no | | |             `option 3` | boolean | no | | |   `pass_steps` | array of string | no | `IfElse` only. Node ids to run when the `pass` branch matches. | |   `human_review_steps` | array of string | no | `IfElse` only. Node ids to run when the `human_review` branch matches; the execution pauses with `needs_human_review: true` until a review decision resumes it. | |   `fail_steps` | array of string | no | `IfElse` only. Node ids to run when no branch matches. | |   `action_type` | string | no | `Action` only. One of: `"Approve"`, `"SendEmail"`, `"AdverseAction"`, `"InternalNotification"`. | |   `email` | object | no | `Action` with `action_type: SendEmail`. Fields support `{{variable}}` interpolation from the execution context. | |     `to` | string | no | | |     `subject` | string | no | | |     `body` | string | no | | |   `adverse_action` | object | no | `Action` with `action_type: AdverseAction`. `send_strategy: draft_first` parks the notice for human review before sending. | |     `recipient` | string | no | | |     `action_taken` | string | no | The action box ticked on the notice: `denied` (application denied), `revoked` (credit revoked or terms changed unfavorably), `adverse_employment` (adverse employment action) or `other_action`. Cascades the AI designer generates always use `denied`. One of: `"denied"`, `"revoked"`, `"adverse_employment"`, `"other_action"`. | |     `send_strategy` | string | no | One of: `"draft_first"`, `"auto_send"`. | |     `reason` | string | no | | |   `internal_notification` | object | no | `Action` with `action_type: InternalNotification`. Operator-facing notification settings. | |   `wait` | object | no | `Wait` only. The delay before the next node runs. | |     `duration` | number | no | | |     `unit` | string | no | One of: `"seconds"`, `"minutes"`, `"hours"`, `"days"`, `"weeks"`. | |   `target_step` | string | no | `GoTo` only. The node id to jump to. | | `source_workflow_id` | integer, nullable | no | | ```json { "id": 1, "object": "workflow_template", "name": "string", "description": "string", "icon": "string", "color": "string", "category": "string", "active": true, "featured": true, "published_at": "2026-05-08T18:41:05Z", "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": { "group_logic": "and", "condition_groups": [ { "logic": "and", "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 minimum required for this property." } } ], "source_workflow_id": 1 } ``` --- --- title: ExecutionStatus schema description: Lifecycle of an execution. queued (batch rows not yet dispatched), pending, running and waiting (paused on a Wait node or adverse-action waiting period)… canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ExecutionStatus" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ExecutionStatus" --- # ExecutionStatus Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ExecutionStatus) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## Allowed values - `queued` - `pending` - `running` - `waiting` - `awaiting_review` - `completed` - `failed` - `cancelled` - `skipped` ```json "queued" ``` --- --- title: ReviewStatus schema description: Allowed values of ReviewStatus in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ReviewStatus" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ReviewStatus" --- # ReviewStatus Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ReviewStatus) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## Allowed values - `none` - `in_review` - `approved` - `denied` ```json "none" ``` --- --- title: ExecutionSummary schema description: The shape returned by triggerWorkflow. Compared with Execution it carries execution_mode and never assigned_to_user_id. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ExecutionSummary" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ExecutionSummary" --- # ExecutionSummary The shape returned by `triggerWorkflow`. Compared with `Execution` it carries `execution_mode` and never `assigned_to_user_id`. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ExecutionSummary) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | | | `object` | string | yes | | | `workflow_id` | integer | yes | | | `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | | `execution_mode` | string | no | One of: `"test"`, `"live"`. | | `execution_key` | string, nullable | no | The namespaced idempotency key (`api_client::`). | | `external_reference` | string, nullable | no | | | `needs_human_review` | boolean | yes | | | `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | | `started_at` | string (date-time), nullable | no | | | `completed_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | yes | | | `updated_at` | string (date-time), nullable | no | | ```json { "id": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_mode": "test", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ``` --- --- title: Execution schema description: The list/detail shape returned by the /executions endpoints. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/Execution" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/Execution" --- # Execution The list/detail shape returned by the `/executions` endpoints. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/Execution) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | | | `object` | string | yes | | | `workflow_id` | integer | yes | | | `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | | `execution_key` | string, nullable | no | | | `external_reference` | string, nullable | no | | | `needs_human_review` | boolean | yes | | | `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | | `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | | `started_at` | string (date-time), nullable | no | | | `completed_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | yes | | | `updated_at` | string (date-time), nullable | no | | ```json { "id": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ``` --- --- title: ExecutionDetail schema description: Fields of the ExecutionDetail object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ExecutionDetail" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ExecutionDetail" --- # ExecutionDetail Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ExecutionDetail) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | | | `object` | string | yes | | | `workflow_id` | integer | yes | | | `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | | `execution_key` | string, nullable | no | | | `external_reference` | string, nullable | no | | | `needs_human_review` | boolean | yes | | | `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | | `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | | `started_at` | string (date-time), nullable | no | | | `completed_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | yes | | | `updated_at` | string (date-time), nullable | no | | | `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | ```json { "id": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "input_context": {} } ``` --- --- title: ExecutionFull schema description: Fields of the ExecutionFull object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ExecutionFull" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ExecutionFull" --- # ExecutionFull Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ExecutionFull) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | yes | | | `object` | string | yes | | | `workflow_id` | integer | yes | | | `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | | `execution_key` | string, nullable | no | | | `external_reference` | string, nullable | no | | | `needs_human_review` | boolean | yes | | | `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | | `assigned_to_user_id` | integer, nullable | no | The reviewer who claimed this execution, if any. | | `started_at` | string (date-time), nullable | no | | | `completed_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | yes | | | `updated_at` | string (date-time), nullable | no | | | `input_context` | object | no | The payload originally sent at trigger time, plus resolved fields such as `execution_mode`, `submitted_by_user_id`, `user_approved`, `external_reference`, (for a test-mode run of a draft) `allow_inactive` and (for a test-mode run that replays a registered test subject) `sandbox_replay`. | | `step_outputs` | object | no | Per-node results keyed by node id. | |   `{key}` | object | no | | | `failed_steps` | array of object | no | | ```json { "id": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "assigned_to_user_id": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "input_context": {}, "step_outputs": { "key": {} }, "failed_steps": [ {} ] } ``` --- --- title: ReviewEvent schema description: Fields of the ReviewEvent object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ReviewEvent" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ReviewEvent" --- # ReviewEvent Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ReviewEvent) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `execution_id` | integer | no | | | `event_type` | string | no | The review events plus the adverse-action notice trail: `adverse_action_drafted` (a notice draft was parked for review), `adverse_action_sent` (the draft was sent), `adverse_action_skipped` (the draft was discarded without sending) and `adverse_action_replayed` (a sent notice was resent from the app). One of: `"flagged"`, `"assigned"`, `"unassigned"`, `"approved"`, `"denied"`, `"note_added"`, `"re_evaluated"`, `"adverse_action_drafted"`, `"adverse_action_sent"`, `"adverse_action_skipped"`, `"adverse_action_replayed"`. | | `actor_user_id` | integer, nullable | no | | | `note` | string, nullable | no | | | `metadata` | object | no | | | `created_at` | string (date-time) | no | | ```json { "id": 1, "object": "review_event", "execution_id": 1, "event_type": "flagged", "actor_user_id": 1, "note": "string", "metadata": {}, "created_at": "2026-05-08T18:41:05Z" } ``` --- --- title: Artifact schema description: Metadata for a downloadable PDF attached to an execution. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/Artifact" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/Artifact" --- # Artifact Metadata for a downloadable PDF attached to an execution. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/Artifact) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | Artifact id; pass it as `artifact_id` to the matching download endpoint. | | `object` | string | no | One of: `"report_artifact"`, `"notice_artifact"`. | | `execution_id` | integer | no | | | `filename` | string | no | | | `content_type` | string | no | | | `byte_size` | integer | no | | | `generated_at` | string (date-time), nullable | no | | | `downloadable` | boolean | no | `false` when the record exists but its file is no longer stored. Downloading it then returns `404 not_found`. | ```json { "id": 1, "object": "report_artifact", "execution_id": 1, "filename": "Adverse_Action_Notice.pdf", "content_type": "application/pdf", "byte_size": 1, "generated_at": "2026-05-08T18:41:05Z", "downloadable": true } ``` --- --- title: ApplicantPayload schema description: Free-form applicant data forwarded to the workflow's first node as context.applicant. Field names depend on the workflow definition. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ApplicantPayload" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ApplicantPayload" --- # ApplicantPayload Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ApplicantPayload) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ```json { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com", "ssn": "123-45-6789", "dob": "1989-04-12" } ``` --- --- title: TriggerRequest schema description: All fields are optional; an empty body starts a test-mode run with an empty payload. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/TriggerRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/TriggerRequest" --- # TriggerRequest All fields are optional; an empty body starts a test-mode run with an empty payload. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/TriggerRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `payload` | object | no | The decision input. Put the subject under `applicant`; arbitrary nested fields are allowed and become the execution's `input_context`. Defaults to `{}` when omitted. | |   `applicant` | ApplicantPayload | no | Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. | |   `property` | object | no | Optional property / tenancy details (as returned by `listWorkflowTestCases`). | | `execution_mode` | string | no | `test` uses synthetic vendor data and never bills; `live` requires production access and an active cascade. One of: `"test"`, `"live"`. | | `external_reference` | string | no | Your correlation id for this run. Echoed on the execution and on every webhook event. | ```json { "payload": { "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com", "ssn": "123-45-6789", "dob": "1989-04-12" }, "property": {} }, "execution_mode": "test", "external_reference": "rv-app-20491" } ``` --- --- title: TriggerResponse schema description: Fields of the TriggerResponse object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/TriggerResponse" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/TriggerResponse" --- # TriggerResponse Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/TriggerResponse) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `data` | ExecutionSummary | no | The shape returned by `triggerWorkflow`. Compared with `Execution` it carries `execution_mode` and never `assigned_to_user_id`. | |   `id` | integer | yes | | |   `object` | string | yes | | |   `workflow_id` | integer | yes | | |   `status` | string | yes | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `execution_key` | string, nullable | no | The namespaced idempotency key (`api_client::`). | |   `external_reference` | string, nullable | no | | |   `needs_human_review` | boolean | yes | | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `started_at` | string (date-time), nullable | no | | |   `completed_at` | string (date-time), nullable | no | | |   `created_at` | string (date-time) | yes | | |   `updated_at` | string (date-time), nullable | no | | | `meta` | Meta | no | | |   `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": 1842, "object": "execution", "workflow_id": 27, "status": "queued", "execution_mode": "test", "execution_key": "api_client:12:3fa85f64-5717-4562-b3fc-2c963f66afa6", "external_reference": "rv-app-20491", "needs_human_review": true, "review_status": "none", "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" }, "meta": { "request_id": "7d4c2f48-9a1b-4c3d-8e2f-1a2b3c4d5e6f" } } ``` --- --- title: ReviewDecisionRequest schema description: Fields of the ReviewDecisionRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ReviewDecisionRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ReviewDecisionRequest" --- # ReviewDecisionRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ReviewDecisionRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `decision` | string | yes | One of: `"approved"`, `"denied"`. | | `note` | string | no | | ```json { "decision": "approved", "note": "string" } ``` --- --- title: ReviewAssignmentRequest schema description: Fields of the ReviewAssignmentRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ReviewAssignmentRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ReviewAssignmentRequest" --- # ReviewAssignmentRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ReviewAssignmentRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `assignee` | string, nullable | no | `me` claims for the calling api_user; null unassigns. One of: `"me"`, `nil`. | | `note` | string | no | | ```json { "assignee": "me", "note": "string" } ``` --- --- title: ReviewNoteRequest schema description: Fields of the ReviewNoteRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/ReviewNoteRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/ReviewNoteRequest" --- # ReviewNoteRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ReviewNoteRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `note` | string | yes | | ```json { "note": "string" } ``` --- --- title: BulkReviewRequest schema description: Fields of the BulkReviewRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/BulkReviewRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/BulkReviewRequest" --- # BulkReviewRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/BulkReviewRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `execution_ids` | array of integer | yes | | | `bulk_action` | string | yes | One of: `"approve"`, `"deny"`, `"assign_to_me"`. | | `note` | string | no | | ```json { "execution_ids": [ 1 ], "bulk_action": "approve", "note": "string" } ``` --- --- title: BulkReviewResult schema description: Fields of the BulkReviewResult object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/BulkReviewResult" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/BulkReviewResult" --- # BulkReviewResult Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/BulkReviewResult) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `object` | string | no | | | `applied` | integer | no | | | `enqueued` | integer | no | | | `skipped` | array of object | no | | |   `id` | integer | no | | |   `reason` | string | no | One of: `"not_awaiting_review"`, `"review_not_pending"`, `"assigned_elsewhere"`, `"live_mode_forbidden"`, `"already_assigned"`, `"not_found"`. | ```json { "object": "bulk_review_result", "applied": 1, "enqueued": 1, "skipped": [ { "id": 1, "reason": "not_awaiting_review" } ] } ``` --- --- title: Batch schema description: Fields of the Batch object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/Batch" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/Batch" --- # Batch Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/Batch) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `workflow_id` | integer | no | | | `status` | string | no | `completed` means every row reached a terminal status, even when some rows failed (see `failed_count`). `failed` means the batch itself stopped on an unexpected error; it is not accepted by `filter[status]` on `listBatches`. One of: `"pending"`, `"running"`, `"interrupted"`, `"completed"`, `"cancelled"`, `"failed"`. | | `execution_mode` | string | no | One of: `"test"`, `"live"`. | | `external_reference` | string, nullable | no | | | `total_count` | integer | no | | | `completed_count` | integer | no | | | `failed_count` | integer | no | | | `started_at` | string (date-time), nullable | no | | | `completed_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | no | | | `updated_at` | string (date-time) | no | | ```json { "id": 77, "object": "batch", "workflow_id": 1, "status": "pending", "execution_mode": "test", "external_reference": "rentvine-batch-001", "total_count": 1, "completed_count": 1, "failed_count": 1, "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ``` --- --- title: BatchExecutionRow schema description: "Subset of Execution fields surfaced when listing the children of a batch. Tokens with executions:read:full additionally receive applicant_name and…" canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/BatchExecutionRow" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/BatchExecutionRow" --- # BatchExecutionRow Subset of Execution fields surfaced when listing the children of a batch. Tokens with `executions:read:full` additionally receive `applicant_name` and `applicant_email`. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/BatchExecutionRow) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `batch_execution_id` | integer | no | | | `workflow_id` | integer | no | | | `row_index` | integer | no | 0-based position in the submitted `rows` array. | | `status` | string | no | Lifecycle of an execution. `queued` (batch rows not yet dispatched), `pending`, `running` and `waiting` (paused on a Wait node or adverse-action waiting period) are in flight; `awaiting_review` is paused on human review; `completed`, `failed`, `cancelled` and `skipped` are terminal. One of: `"queued"`, `"pending"`, `"running"`, `"waiting"`, `"awaiting_review"`, `"completed"`, `"failed"`, `"cancelled"`, `"skipped"`. | | `retry_count` | integer | no | | | `external_reference` | string, nullable | no | | | `needs_human_review` | boolean | no | | | `review_status` | string | no | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | | `started_at` | string (date-time), nullable | no | | | `completed_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | no | | | `applicant_name` | string, nullable | no | Only with `executions:read:full`. | | `applicant_email` | string, nullable | no | Only with `executions:read:full`. | ```json { "id": 1, "object": "execution", "batch_execution_id": 1, "workflow_id": 1, "row_index": 1, "status": "queued", "retry_count": 1, "external_reference": "string", "needs_human_review": true, "review_status": "none", "started_at": "2026-05-08T18:41:05Z", "completed_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "applicant_name": "string", "applicant_email": "string" } ``` --- --- title: BatchCreateRequest schema description: Fields of the BatchCreateRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/BatchCreateRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/BatchCreateRequest" --- # BatchCreateRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/BatchCreateRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `rows` | array of object | yes | One object per applicant. Every row must be an object. | |   `external_reference` | string | no | | |   `applicant` | ApplicantPayload | no | Free-form applicant data forwarded to the workflow's first node as `context.applicant`. Field names depend on the workflow definition. | | `execution_mode` | string | no | One of: `"test"`, `"live"`. | | `external_reference` | string | no | | ```json { "rows": [ { "external_reference": "string", "applicant": { "first_name": "Jordan", "last_name": "Lee", "email": "jordan@example.com", "ssn": "123-45-6789", "dob": "1989-04-12" } } ], "execution_mode": "test", "external_reference": "string" } ``` --- --- title: WebhookEventName schema description: Lifecycle events Cascades can deliver. See x-webhooks for each payload. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WebhookEventName" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WebhookEventName" --- # WebhookEventName Lifecycle events Cascades can deliver. See `x-webhooks` for each payload. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WebhookEventName) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## Allowed values - `execution.completed` - `execution.failed` - `adverse_action.triggered` - `adverse_action.completed` - `adverse_action.failed` - `adverse_action.retry_scheduled` ```json "execution.completed" ``` --- --- title: WebhookEnvelope schema description: The body of every webhook delivery. data is event-specific — see x-webhooks. Nil-valued fields are omitted from data rather than sent as null. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WebhookEnvelope" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WebhookEnvelope" --- # WebhookEnvelope The body of every webhook delivery. `data` is event-specific — see `x-webhooks`. Nil-valued fields are omitted from `data` rather than sent as `null`. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WebhookEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | Event id. Identical across every subscriber's copy of the same event and across retries; use it to de-duplicate. | | `event` | string | yes | Lifecycle events Cascades can deliver. See `x-webhooks` for each payload. One of: `"execution.completed"`, `"execution.failed"`, `"adverse_action.triggered"`, `"adverse_action.completed"`, `"adverse_action.failed"`, `"adverse_action.retry_scheduled"`. | | `created_at` | string (date-time) | yes | When the event was emitted (not when this attempt was sent). | | `data` | object | yes | | ```json { "id": "6d1f1a5e-4a2b-4c7d-9e8f-0a1b2c3d4e5f", "event": "execution.completed", "created_at": "2026-05-08T18:41:05Z", "data": {} } ``` --- --- title: WebhookSubscription schema description: Fields of the WebhookSubscription object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WebhookSubscription" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WebhookSubscription" --- # WebhookSubscription Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WebhookSubscription) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `url` | string | no | | | `event_filter` | array of WebhookEventName | no | Empty = subscribed to ALL events. | | `active` | boolean | no | | | `last_delivered_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | no | | | `updated_at` | string (date-time) | no | | ```json { "id": 1, "object": "webhook_subscription", "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed" ], "active": true, "last_delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ``` --- --- title: WebhookSubscriptionWithSecret schema description: Fields of the WebhookSubscriptionWithSecret object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WebhookSubscriptionWithSecret" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WebhookSubscriptionWithSecret" --- # WebhookSubscriptionWithSecret Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WebhookSubscriptionWithSecret) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `url` | string | no | | | `event_filter` | array of WebhookEventName | no | Empty = subscribed to ALL events. | | `active` | boolean | no | | | `last_delivered_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | no | | | `updated_at` | string (date-time) | no | | | `signing_secret` | string | no | | ```json { "id": 1, "object": "webhook_subscription", "url": "https://hooks.partner.example/cascades", "event_filter": [ "execution.completed" ], "active": true, "last_delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z", "signing_secret": "whsec_4f1b2c3d4e5f60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f9" } ``` --- --- title: WebhookSubscriptionCreateRequest schema description: Fields of the WebhookSubscriptionCreateRequest object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WebhookSubscriptionCreateRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WebhookSubscriptionCreateRequest" --- # WebhookSubscriptionCreateRequest Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WebhookSubscriptionCreateRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `url` | string (uri) | yes | HTTPS endpoint. Must resolve to a public address (no private, loopback or cloud-metadata ranges). | | `event_filter` | array of WebhookEventName | no | Events to receive. Omit or pass `[]` for all events; unknown names return `422`. | | `active` | boolean | no | | ```json { "url": "https://example.com", "event_filter": [ "execution.completed" ], "active": true } ``` --- --- title: WebhookSubscriptionUpdateRequest schema description: PATCH semantics — only the keys present change. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WebhookSubscriptionUpdateRequest" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WebhookSubscriptionUpdateRequest" --- # WebhookSubscriptionUpdateRequest PATCH semantics — only the keys present change. Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WebhookSubscriptionUpdateRequest) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `url` | string (uri) | no | | | `event_filter` | array of WebhookEventName | no | | | `active` | boolean | no | | ```json { "url": "https://example.com", "event_filter": [ "execution.completed" ], "active": true } ``` --- --- title: WebhookDelivery schema description: Fields of the WebhookDelivery object in the Cascades API. canonical_url: "https://cascades.crscreditapi.com/docs/api/schemas/WebhookDelivery" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/components/schemas/WebhookDelivery" --- # WebhookDelivery Defined in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/WebhookDelivery) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). | Field | Type | Required | Description | |---|---|---|---| | `id` | integer | no | | | `object` | string | no | | | `webhook_subscription_id` | integer | no | | | `event_type` | string | no | Lifecycle events Cascades can deliver. See `x-webhooks` for each payload. One of: `"execution.completed"`, `"execution.failed"`, `"adverse_action.triggered"`, `"adverse_action.completed"`, `"adverse_action.failed"`, `"adverse_action.retry_scheduled"`. | | `status` | string | no | One of: `"pending"`, `"retrying"`, `"succeeded"`, `"failed"`, `"dead"`. | | `attempts` | integer | no | | | `max_attempts` | integer | no | | | `last_response_status` | integer, nullable | no | | | `last_attempted_at` | string (date-time), nullable | no | | | `next_retry_at` | string (date-time), nullable | no | | | `delivered_at` | string (date-time), nullable | no | | | `created_at` | string (date-time) | no | | | `updated_at` | string (date-time) | no | | ```json { "id": 1, "object": "webhook_delivery", "webhook_subscription_id": 1, "event_type": "execution.completed", "status": "pending", "attempts": 1, "max_attempts": 6, "last_response_status": 1, "last_attempted_at": "2026-05-08T18:41:05Z", "next_retry_at": "2026-05-08T18:41:05Z", "delivered_at": "2026-05-08T18:41:05Z", "created_at": "2026-05-08T18:41:05Z", "updated_at": "2026-05-08T18:41:05Z" } ``` --- --- title: API error codes description: Every error code the Cascades API returns, grouped by HTTP status, with the envelope shape, causes and fixes. canonical_url: "https://cascades.crscreditapi.com/docs/api/errors" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: config/api_error_codes.yml --- # API error codes Every failed request returns one JSON envelope with a stable machine-readable `code`. The `doc_url` in that envelope links to the page for its code. The envelope schema is `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` | Field | Type | Meaning | | --- | --- | --- | | `code` | string | Stable identifier for the failure. Branch on this, never on `message`. | | `message` | string | Human-readable summary. Wording may change. | | `details` | array | Zero or more `{ field, message }` entries naming the parameter, filter, or scope at fault. | | `request_id` | string | Correlation id, also sent as the `X-Request-Id` header. | | `doc_url` | string | Link to the page for this `code` on the origin you called. | Rate limits (`429`) also send a `Retry-After` header in seconds; every `401` carries an RFC 6750 `WWW-Authenticate` challenge. ## 400 Bad Request | Code | Title | Message | | --- | --- | --- | | [`bad_request`](https://cascades.crscreditapi.com/docs/api/errors/bad_request.md) | Bad request | A required parameter is missing. | | [`malformed_body`](https://cascades.crscreditapi.com/docs/api/errors/malformed_body.md) | Malformed request body | The request body could not be parsed. Send valid JSON matching the Content-Type header. | ## 401 Unauthorized | Code | Title | Message | | --- | --- | --- | | [`unauthorized`](https://cascades.crscreditapi.com/docs/api/errors/unauthorized.md) | Unauthorized | A valid bearer token is required. | ## 403 Forbidden | Code | Title | Message | | --- | --- | --- | | [`insufficient_scope`](https://cascades.crscreditapi.com/docs/api/errors/insufficient_scope.md) | Insufficient scope | This token does not have the required scope. | | [`forbidden`](https://cascades.crscreditapi.com/docs/api/errors/forbidden.md) | Forbidden | You are not authorized to perform this action. | | [`live_mode_forbidden`](https://cascades.crscreditapi.com/docs/api/errors/live_mode_forbidden.md) | Live mode forbidden | Live mode requires production access. | | [`activation_forbidden`](https://cascades.crscreditapi.com/docs/api/errors/activation_forbidden.md) | Activation forbidden | Activating a cascade requires production access. It stays a draft, which runs in test mode. | | [`review_assigned_elsewhere`](https://cascades.crscreditapi.com/docs/api/errors/review_assigned_elsewhere.md) | Review assigned to another user | This review is assigned to another user. | ## 404 Not Found | Code | Title | Message | | --- | --- | --- | | [`not_found`](https://cascades.crscreditapi.com/docs/api/errors/not_found.md) | Not found | The requested resource was not found. | ## 405 Method Not Allowed | Code | Title | Message | | --- | --- | --- | | [`method_not_allowed`](https://cascades.crscreditapi.com/docs/api/errors/method_not_allowed.md) | Method not allowed | The request could not be completed. | ## 406 Not Acceptable | Code | Title | Message | | --- | --- | --- | | [`not_acceptable`](https://cascades.crscreditapi.com/docs/api/errors/not_acceptable.md) | Not acceptable | The request could not be completed. | | [`unsupported_format`](https://cascades.crscreditapi.com/docs/api/errors/unsupported_format.md) | Unsupported export format | Supported formats are .csv and .zip. | ## 409 Conflict | Code | Title | Message | | --- | --- | --- | | [`workflow_inactive`](https://cascades.crscreditapi.com/docs/api/errors/workflow_inactive.md) | Workflow inactive | Only active workflows can run batches through the public API. | | [`conflict`](https://cascades.crscreditapi.com/docs/api/errors/conflict.md) | Conflict | The request could not be completed. | | [`idempotency_conflict`](https://cascades.crscreditapi.com/docs/api/errors/idempotency_conflict.md) | Idempotency key reused | The Idempotency-Key has already been used with a different request payload. | | [`execution_not_cancellable`](https://cascades.crscreditapi.com/docs/api/errors/execution_not_cancellable.md) | Execution not cancellable | Execution is already . | | [`review_not_pending`](https://cascades.crscreditapi.com/docs/api/errors/review_not_pending.md) | Review not pending | Execution is not awaiting human review. | | [`review_already_decided`](https://cascades.crscreditapi.com/docs/api/errors/review_already_decided.md) | Review already decided | Review was already . | | [`batch_not_cancellable`](https://cascades.crscreditapi.com/docs/api/errors/batch_not_cancellable.md) | Batch not cancellable | Batch is in status and cannot be cancelled. | | [`batch_not_resumable`](https://cascades.crscreditapi.com/docs/api/errors/batch_not_resumable.md) | Batch not resumable | Batch is in status and cannot be resumed. | | [`batch_not_retryable`](https://cascades.crscreditapi.com/docs/api/errors/batch_not_retryable.md) | Batch has no retryable rows | Batch is in status and has no retryable rows. | | [`delivery_not_retryable`](https://cascades.crscreditapi.com/docs/api/errors/delivery_not_retryable.md) | Delivery not retryable | Delivery is in status and cannot be retried. | ## 422 Unprocessable Content | Code | Title | Message | | --- | --- | --- | | [`unprocessable_content`](https://cascades.crscreditapi.com/docs/api/errors/unprocessable_content.md) | Unprocessable content | The request could not be completed. | | [`validation_failed`](https://cascades.crscreditapi.com/docs/api/errors/validation_failed.md) | Validation failed | The request could not be processed. | | [`missing_idempotency_key`](https://cascades.crscreditapi.com/docs/api/errors/missing_idempotency_key.md) | Idempotency-Key header missing | Idempotency-Key header is required. | | [`generation_failed`](https://cascades.crscreditapi.com/docs/api/errors/generation_failed.md) | Cascade generation failed | | ## 429 Too Many Requests | Code | Title | Message | | --- | --- | --- | | [`rate_limited`](https://cascades.crscreditapi.com/docs/api/errors/rate_limited.md) | Rate limited | Rate limit exceeded. Retry later. | ## 500 Internal Server Error | Code | Title | Message | | --- | --- | --- | | [`internal_server_error`](https://cascades.crscreditapi.com/docs/api/errors/internal_server_error.md) | Internal server error | The request could not be completed. | ## 501 Not Implemented | Code | Title | Message | | --- | --- | --- | | [`not_implemented`](https://cascades.crscreditapi.com/docs/api/errors/not_implemented.md) | Not implemented | The request could not be completed. | ## 503 Service Unavailable | Code | Title | Message | | --- | --- | --- | | [`designer_busy`](https://cascades.crscreditapi.com/docs/api/errors/designer_busy.md) | Cascade designer busy | The cascade designer is busy right now. Please try again in a moment. | --- --- title: bad_request — Bad request description: "HTTP 400 bad_request: A required parameter is missing." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/bad_request" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 400 source: config/api_error_codes.yml --- # `bad_request` — Bad request **HTTP 400 Bad Request** Message: "A required parameter is missing." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - A required top-level parameter was absent from the request body or query string; `details[0].field` names it. - The query string could not be parsed — for example the same key sent as both a scalar and a nested hash (`filter=1&filter[status]=failed`). This variant comes from the framework with the message "The request could not be completed." and no `details`. ## How to fix it 1. Add the parameter named in `details[].field` and resend. 2. Send filters consistently as `filter[]=`; never mix `filter=` with `filter[...]` in one request. 3. Send `Content-Type: application/json` with a JSON object body for write operations. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 400 Bad Request Content-Type: application/json ``` ```json { "error": { "code": "bad_request", "message": "A required parameter is missing.", "details": [ { "field": "rows", "message": "parameter is required" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/bad_request" } } ``` ## Related - [/docs/concepts/pagination-and-filtering](/docs/concepts/pagination-and-filtering) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: malformed_body — Malformed request body description: "HTTP 400 malformed_body: The request body could not be parsed. Send valid JSON matching the Content-Type header." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/malformed_body" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 400 source: config/api_error_codes.yml --- # `malformed_body` — Malformed request body **HTTP 400 Bad Request** Message: "The request body could not be parsed. Send valid JSON matching the Content-Type header." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The request declared `Content-Type: application/json` but the body was not valid JSON (truncated, trailing comma, single quotes, unquoted keys). - The body was double-encoded — a JSON string containing JSON — or wrapped in a form field. ## How to fix it 1. Validate the body with a JSON linter before sending; most HTTP clients serialise a native object for you (`json=` in requests, `body: JSON.stringify(...)` in fetch). 2. Match `Content-Type` to the encoding actually used. Form-encoded bodies must not claim `application/json`. ## Operations that return it - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription ## Example response ```http HTTP/1.1 400 Bad Request Content-Type: application/json ``` ```json { "error": { "code": "malformed_body", "message": "The request body could not be parsed. Send valid JSON matching the Content-Type header.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/malformed_body" } } ``` [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: unauthorized — Unauthorized description: "HTTP 401 unauthorized: A valid bearer token is required." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/unauthorized" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 401 source: config/api_error_codes.yml --- # `unauthorized` — Unauthorized **HTTP 401 Unauthorized** Message: "A valid bearer token is required." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - No `Authorization: Bearer ` header was sent (the response carries `WWW-Authenticate: Bearer realm="api"`). - The token is unknown, expired (default lifetime is 90 days), revoked, or belongs to a deactivated API client (`WWW-Authenticate: Bearer error="invalid_token"`). - The header used a different scheme (`Basic`, `Token`) or the token was sent as a query parameter. ## How to fix it 1. Send the key exactly as `Authorization: Bearer `; the scheme is case-insensitive, the key is not. 2. Mint a fresh key from the API & MCP page if yours has expired or was revoked, and rotate it in your secrets store. 3. Never retry a 401 in a loop — repeated unknown-token attempts from one IP earn a temporary `rate_limited` ban. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 401 Unauthorized Content-Type: application/json ``` ```json { "error": { "code": "unauthorized", "message": "A valid bearer token is required.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/unauthorized" } } ``` ## Related - [/docs/concepts/scopes-and-keys](/docs/concepts/scopes-and-keys) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: insufficient_scope — Insufficient scope description: "HTTP 403 insufficient_scope: This token does not have the required scope." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/insufficient_scope" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 403 source: config/api_error_codes.yml --- # `insufficient_scope` — Insufficient scope **HTTP 403 Forbidden** Message: "This token does not have the required scope." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The token authenticated, but its scope list does not include every scope the operation requires; `details[].message` lists the missing scopes (for example `executions:write`). - A read token is being used for a write operation, or an `executions:read` token is downloading artifacts or exports that need `executions:read:full`. ## How to fix it 1. Use a key issued with the scopes listed in `details`. Keys minted from the API & MCP page carry the full default set. 2. Check scope before probing IDs — scope is enforced before the record lookup, so a 403 here says nothing about whether the resource exists. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 403 Forbidden Content-Type: application/json ``` ```json { "error": { "code": "insufficient_scope", "message": "This token does not have the required scope.", "details": [ { "field": "scope", "message": "executions:write" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/insufficient_scope" } } ``` ## Related - [/docs/concepts/scopes-and-keys](/docs/concepts/scopes-and-keys) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: forbidden — Forbidden description: "HTTP 403 forbidden: You are not authorized to perform this action." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/forbidden" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 403 source: config/api_error_codes.yml --- # `forbidden` — Forbidden **HTTP 403 Forbidden** Message: "You are not authorized to perform this action." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The authenticated user is not the owner of the workflow (or of the execution's or batch's workflow) the request targets. Ownership checks run after scope checks and after the record lookup. - A template lookup was made with a token whose API client has no user (an orphaned client). ## How to fix it 1. Use the key belonging to the account that owns the cascade. Workflow and execution IDs are not shared across accounts. 2. Confirm the id with the list endpoint for the same key (`listWorkflows`, `listExecutions`, `listBatches`) before acting on it. ## Operations that return it - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF ## Example response ```http HTTP/1.1 403 Forbidden Content-Type: application/json ``` ```json { "error": { "code": "forbidden", "message": "You are not authorized to perform this action.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/forbidden" } } ``` [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: live_mode_forbidden — Live mode forbidden description: "HTTP 403 live_mode_forbidden: Live mode requires production access." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/live_mode_forbidden" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 403 source: config/api_error_codes.yml --- # `live_mode_forbidden` — Live mode forbidden **HTTP 403 Forbidden** Message: "Live mode requires production access." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The request asked for `execution_mode: "live"` (trigger, batch create) but the account has not been approved for production data. - The request used a docs key (the key these docs pages mint for you when you are signed in). Docs keys are sandbox-only: they never run live, even on an approved account. The message then reads "Docs keys are sandbox-only, so they never run live. Use a key from /api-keys for live mode." - A live-mode batch or execution is being resumed, retried, or re-evaluated by an account that no longer holds production access. - A live-mode review decision was attempted, either by an account without production access or after the original submitter lost it ("Live-mode review cannot resume because the original submitter no longer has production access."). ## How to fix it 1. Omit `execution_mode` or send `"test"`. Test mode runs every cascade at no charge, on mock data or on a registered test subject's authored reports. 2. With a docs key, use a key from /api-keys (or the Cascades app) for live runs. Keep the docs key for test mode. 3. Request production approval from your Cascades contact. Approval is manual; there is no self-serve switch. Once approved, your /api-keys key works for live mode. Docs keys never do. 4. For a blocked review resume, have an approved user on the account record the decision. ## Operations that return it - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review ## Example response ```http HTTP/1.1 403 Forbidden Content-Type: application/json ``` ```json { "error": { "code": "live_mode_forbidden", "message": "Live mode requires production access.", "details": [ { "field": "execution_mode", "message": "live mode requires production access" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/live_mode_forbidden" } } ``` ## Related - [/docs/concepts/sandbox-vs-live](/docs/concepts/sandbox-vs-live) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: activation_forbidden — Activation forbidden description: "HTTP 403 activation_forbidden: Activating a cascade requires production access. It stays a draft, which runs in test mode." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/activation_forbidden" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 403 source: config/api_error_codes.yml --- # `activation_forbidden` — Activation forbidden **HTTP 403 Forbidden** Message: "Activating a cascade requires production access. It stays a draft, which runs in test mode." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - `PATCH /workflows/{id}` sent `active: true` from an account without production access. An explicit activation is refused rather than silently dropped. - The request used a docs key (the key these docs pages mint for you when you are signed in). Docs keys are sandbox-only, so they cannot activate a cascade even on an approved account. The message then reads "Docs keys are sandbox-only, so they cannot activate a cascade. Activate it in the app or with a key from /api-keys. It stays a draft, which runs in test mode." - Note that `POST /workflows` and `POST /workflows/generate` do not raise this — they save the cascade as a draft and add a `note` to the response instead. ## How to fix it 1. Leave `active` out of the update; a draft cascade can still be triggered in test mode. 2. With a docs key, activate the cascade in the Cascades app, or send the same request with a key from /api-keys. 3. Without production access, request production approval, then activate. ## Operations that return it - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow ## Example response ```http HTTP/1.1 403 Forbidden Content-Type: application/json ``` ```json { "error": { "code": "activation_forbidden", "message": "Activating a cascade requires production access. It stays a draft, which runs in test mode.", "details": [ { "field": "active", "message": "requires production access" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/activation_forbidden" } } ``` ## Related - [/docs/concepts/sandbox-vs-live](/docs/concepts/sandbox-vs-live) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: review_assigned_elsewhere — Review assigned to another user description: "HTTP 403 review_assigned_elsewhere: This review is assigned to another user." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/review_assigned_elsewhere" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 403 source: config/api_error_codes.yml --- # `review_assigned_elsewhere` — Review assigned to another user **HTTP 403 Forbidden** Message: "This review is assigned to another user." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The execution's review is assigned to a different user on the account, and the caller is neither that user nor an admin. ## How to fix it 1. Claim the review first with `POST /executions/{id}/review_assignment` and `assignee: "me"`, then decide. 2. Or unassign it (`assignee: null`) if the current assignee has abandoned it. ## Operations that return it - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review ## Example response ```http HTTP/1.1 403 Forbidden Content-Type: application/json ``` ```json { "error": { "code": "review_assigned_elsewhere", "message": "This review is assigned to another user.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/review_assigned_elsewhere" } } ``` ## Related - [/docs/concepts/human-review](/docs/concepts/human-review) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: not_found — Not found description: "HTTP 404 not_found: The requested resource was not found." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/not_found" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 404 source: config/api_error_codes.yml --- # `not_found` — Not found **HTTP 404 Not Found** Message: "The requested resource was not found." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - No workflow, execution, batch, template, webhook subscription or delivery with that id is visible to this API client. Executions and batches are scoped to the client that created them, so a second client on the same account cannot see them. - The path does not exist (`/api/v1/...` catch-all), or an artifact id is not attached to that execution ("Artifact not found."). - `GET /batches/{id}/adverse_action_notices.zip` when no notices have been generated for any row ("No adverse-action notices have been generated for this batch."). - A soft-deleted workflow. ## How to fix it 1. List the collection with the same key and use an id from that response. 2. Check the path against the reference — ids are integers, and there is no trailing-slash tolerance on nested routes. 3. For artifacts, poll the execution until `status` is terminal; PDFs and notices attach after the run completes. ## Operations that return it Any operation can return this code, as can an unknown path under `/api/v1`. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 404 Not Found Content-Type: application/json ``` ```json { "error": { "code": "not_found", "message": "The requested resource was not found.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_found" } } ``` [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: method_not_allowed — Method not allowed description: "HTTP 405 method_not_allowed: The request could not be completed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/method_not_allowed" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 405 source: config/api_error_codes.yml --- # `method_not_allowed` — Method not allowed **HTTP 405 Method Not Allowed** Message: "The request could not be completed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The HTTP method is not one the API accepts. In practice the web server refuses an unknown verb such as `FOO` first, with a plain-text `501 Not Implemented` and no envelope, so this code is rare. A known verb that the path does not implement (for example `DELETE /workflows`) answers `404 not_found` instead. ## How to fix it 1. Use the method shown on the operation's reference page. Retries and cancels are `POST`, updates are `PATCH` (or `PUT` for webhook subscriptions), deletes are `DELETE`. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 405 Method Not Allowed Content-Type: application/json ``` ```json { "error": { "code": "method_not_allowed", "message": "The request could not be completed.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/method_not_allowed" } } ``` [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: not_acceptable — Not acceptable description: "HTTP 406 not_acceptable: The request could not be completed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/not_acceptable" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 406 source: config/api_error_codes.yml --- # `not_acceptable` — Not acceptable **HTTP 406 Not Acceptable** Message: "The request could not be completed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The `Accept` header is not a valid media type (for example `Accept: json`, with no slash) or names a format the API does not serve. This comes from the framework with no `details`. ## How to fix it 1. Send `Accept: application/json`, or omit the `Accept` header — every operation answers JSON by default. 2. For batch exports, choose the format with the path extension (`.csv` / `.zip`) rather than `Accept`; see `unsupported_format`. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 406 Not Acceptable Content-Type: application/json ``` ```json { "error": { "code": "not_acceptable", "message": "The request could not be completed.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_acceptable" } } ``` ## Related - [/docs/api/errors/unsupported_format](/docs/api/errors/unsupported_format) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: unsupported_format — Unsupported export format description: "HTTP 406 unsupported_format: Supported formats are .csv and .zip." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/unsupported_format" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 406 source: config/api_error_codes.yml --- # `unsupported_format` — Unsupported export format **HTTP 406 Not Acceptable** Message: "Supported formats are .csv and .zip." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - `GET /batches/{id}/exports` was requested with an extension other than `.csv` or `.zip`, or with an `Accept` header that resolved to another format. ## How to fix it 1. Request `/batches/{id}/exports.csv` for the flat results file or `/batches/{id}/exports.zip` for results plus per-row report PDFs. The bare path defaults to CSV. ## Operations that return it - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results ## Example response ```http HTTP/1.1 406 Not Acceptable Content-Type: application/json ``` ```json { "error": { "code": "unsupported_format", "message": "Supported formats are .csv and .zip.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/unsupported_format" } } ``` ## Related - [/docs/concepts/batches](/docs/concepts/batches) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: workflow_inactive — Workflow inactive description: "HTTP 409 workflow_inactive: Only active workflows can run batches through the public API." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/workflow_inactive" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `workflow_inactive` — Workflow inactive **HTTP 409 Conflict** Message: "Only active workflows can run batches through the public API." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - A live-mode trigger on a draft cascade ("Draft workflows run in test mode only. Activate the workflow before triggering it live."). Test-mode triggers on drafts are allowed. - A batch create, resume, or retry on a cascade that is not active. Batches need an active cascade in test mode too, so a sandbox account (not approved for production) gets this on every batch create. - A re-evaluation of an execution whose cascade has since been deactivated. ## How to fix it 1. Activate the cascade (`PATCH /workflows/{id}` with `active: true`, or the Activate toggle in the builder) and resend. 2. For a one-off test run against a draft, trigger it with `execution_mode: "test"`. 3. A sandbox account cannot activate a cascade, so it cannot run batches. Get approved for production through the activation form at /activate first. ## Operations that return it - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "workflow_inactive", "message": "Only active workflows can run batches through the public API.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/workflow_inactive" } } ``` ## Related - [/docs/concepts/cascades](/docs/concepts/cascades) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: conflict — Conflict description: "HTTP 409 conflict: The request could not be completed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/conflict" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `conflict` — Conflict **HTTP 409 Conflict** Message: "The request could not be completed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - Two writes raced on the same record — for example two simultaneous requests carrying the same `Idempotency-Key`, or a concurrent update that tripped a uniqueness or optimistic-lock check. This comes from the framework with no `details`. ## How to fix it 1. Retry once after a short delay. Reuse the same `Idempotency-Key` where the operation accepts one, so the retry is safe. 2. Serialise writes to one record on your side rather than issuing them in parallel. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "conflict", "message": "The request could not be completed.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/conflict" } } ``` ## Related - [/docs/concepts/idempotency](/docs/concepts/idempotency) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: idempotency_conflict — Idempotency key reused description: "HTTP 409 idempotency_conflict: The Idempotency-Key has already been used with a different request payload." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/idempotency_conflict" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `idempotency_conflict` — Idempotency key reused **HTTP 409 Conflict** Message: "The Idempotency-Key has already been used with a different request payload." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The same `Idempotency-Key` was sent by this API client with a different `payload`, `execution_mode`, or `external_reference` (trigger), or a different row set (batch). Keys are compared per client, so two clients may reuse a value. - A key was recycled after the client-side request object changed — for example a retry that regenerated a timestamp inside the payload. ## How to fix it 1. Generate a new key (a UUID) for every distinct request; reuse a key only to retry the byte-identical request, which returns the original execution or batch. 2. Store the key alongside the request in your own system before sending, so a crash between send and response can be replayed safely. ## Operations that return it - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "idempotency_conflict", "message": "The Idempotency-Key has already been used with a different request payload.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/idempotency_conflict" } } ``` ## Related - [/docs/concepts/idempotency](/docs/concepts/idempotency) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: execution_not_cancellable — Execution not cancellable description: "HTTP 409 execution_not_cancellable: Execution is already status." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/execution_not_cancellable" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `execution_not_cancellable` — Execution not cancellable **HTTP 409 Conflict** Message: "Execution is already ." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The execution is not in `pending`, `running`, or `waiting`; it already completed, failed, or was cancelled. `details[0].message` carries the current status. ## How to fix it 1. Treat the execution as final and read its decision with `GET /executions/{id}`. 2. To run the applicant again, use `POST /executions/{id}/re_evaluations` or trigger the cascade with a new idempotency key. ## Operations that return it - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "execution_not_cancellable", "message": "Execution is already .", "details": [ { "field": "status", "message": "completed" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/execution_not_cancellable" } } ``` ## Related - [/docs/concepts/executions](/docs/concepts/executions) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: review_not_pending — Review not pending description: "HTTP 409 review_not_pending: Execution is not awaiting human review." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/review_not_pending" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `review_not_pending` — Review not pending **HTTP 409 Conflict** Message: "Execution is not awaiting human review." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The execution never paused for review (`needs_human_review` is false), so there is nothing to decide. - The execution flagged review earlier but has since finished, failed, or been cancelled ("Execution is in status and cannot be resumed."). ## How to fix it 1. Poll or subscribe to `execution.review_required`; only executions whose `review_status` is `pending` accept a decision. 2. Filter the list with `filter[needs_human_review]=true&filter[status]=awaiting_review` to find the ones waiting on you. ## Operations that return it - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "review_not_pending", "message": "Execution is not awaiting human review.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/review_not_pending" } } ``` ## Related - [/docs/concepts/human-review](/docs/concepts/human-review) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: review_already_decided — Review already decided description: "HTTP 409 review_already_decided: Review was already approved|denied." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/review_already_decided" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `review_already_decided` — Review already decided **HTTP 409 Conflict** Message: "Review was already ." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - A decision was already recorded for this execution, by the API, the operator UI, or a bulk action. Decisions are final. ## How to fix it 1. Read the recorded outcome from `review_status` and `review_events`; do not resend. 2. If the outcome was wrong, re-evaluate the applicant with `POST /executions/{id}/re_evaluations`, which produces a fresh execution and a fresh review. ## Operations that return it - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "review_already_decided", "message": "Review was already .", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/review_already_decided" } } ``` ## Related - [/docs/concepts/human-review](/docs/concepts/human-review) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: batch_not_cancellable — Batch not cancellable description: "HTTP 409 batch_not_cancellable: Batch is in status status and cannot be cancelled." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/batch_not_cancellable" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `batch_not_cancellable` — Batch not cancellable **HTTP 409 Conflict** Message: "Batch is in status and cannot be cancelled." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The batch is not `pending`, `running`, or `interrupted` — it already completed, failed, or was cancelled. ## How to fix it 1. Read the final counts with `GET /batches/{id}` and the per-row outcomes with `GET /batches/{id}/executions`. ## Operations that return it - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "batch_not_cancellable", "message": "Batch is in status and cannot be cancelled.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/batch_not_cancellable" } } ``` ## Related - [/docs/concepts/batches](/docs/concepts/batches) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: batch_not_resumable — Batch not resumable description: "HTTP 409 batch_not_resumable: Batch is in status status and cannot be resumed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/batch_not_resumable" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `batch_not_resumable` — Batch not resumable **HTTP 409 Conflict** Message: "Batch is in status and cannot be resumed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - Resume applies only to an `interrupted` batch that still has `queued` rows (a worker restart or a cancel mid-run). Completed, running, or cancelled batches cannot be resumed. ## How to fix it 1. For a finished batch with failed rows, use `POST /batches/{id}/retry_failed` instead. 2. If the batch is still `running`, wait for `batch.completed` or poll `GET /batches/{id}`. ## Operations that return it - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "batch_not_resumable", "message": "Batch is in status and cannot be resumed.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/batch_not_resumable" } } ``` ## Related - [/docs/concepts/batches](/docs/concepts/batches) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: batch_not_retryable — Batch has no retryable rows description: "HTTP 409 batch_not_retryable: Batch is in status status and has no retryable rows." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/batch_not_retryable" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `batch_not_retryable` — Batch has no retryable rows **HTTP 409 Conflict** Message: "Batch is in status and has no retryable rows." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The batch is not `completed` or `interrupted`, or it has no `failed` rows under the retry limit (3 attempts per row) and no `skipped` rows. ## How to fix it 1. Inspect `GET /batches/{id}/executions?filter[status]=failed` — rows that exhausted their retries stay failed and should be resubmitted in a new batch. 2. Wait for a running batch to finish before retrying. ## Operations that return it - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "batch_not_retryable", "message": "Batch is in status and has no retryable rows.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/batch_not_retryable" } } ``` ## Related - [/docs/concepts/batches](/docs/concepts/batches) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: delivery_not_retryable — Delivery not retryable description: "HTTP 409 delivery_not_retryable: Delivery is in status status and cannot be retried." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/delivery_not_retryable" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 409 source: config/api_error_codes.yml --- # `delivery_not_retryable` — Delivery not retryable **HTTP 409 Conflict** Message: "Delivery is in status and cannot be retried." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The delivery already `succeeded`, or a worker currently holds a claim on it (an automatic retry is in flight). ## How to fix it 1. Only `failed` and `exhausted` deliveries need a manual retry; check `status` and `next_retry_at` first. 2. If the endpoint was down, fix it and retry once — each manual retry restarts the attempt counter's schedule, it does not add attempts. ## Operations that return it - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 409 Conflict Content-Type: application/json ``` ```json { "error": { "code": "delivery_not_retryable", "message": "Delivery is in status and cannot be retried.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/delivery_not_retryable" } } ``` ## Related - [/docs/concepts/webhooks](/docs/concepts/webhooks) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: unprocessable_content — Unprocessable content description: "HTTP 422 unprocessable_content: The request could not be completed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/unprocessable_content" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 422 source: config/api_error_codes.yml --- # `unprocessable_content` — Unprocessable content **HTTP 422 Unprocessable Content** Message: "The request could not be completed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - A record could not be persisted for a reason the `validation_failed` path did not report — the framework raised while saving. This comes with no `details`. ## How to fix it 1. Check the request body against the operation's schema and resend. 2. If it persists, contact support with the `request_id`, the operation, and the approximate time. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 422 Unprocessable Content Content-Type: application/json ``` ```json { "error": { "code": "unprocessable_content", "message": "The request could not be completed.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/unprocessable_content" } } ``` ## Related - [/docs/api/errors/validation_failed](/docs/api/errors/validation_failed) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: validation_failed — Validation failed description: "HTTP 422 validation_failed: The request could not be processed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 422 source: config/api_error_codes.yml --- # `validation_failed` — Validation failed **HTTP 422 Unprocessable Content** Message: "The request could not be processed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - A field has a value outside its allowed set — `execution_mode` must be `test` or `live`; `decision` must be `approved` or `denied`; `bulk_action` must be `approve`, `deny`, or `assign_to_me`; `assignee` must be `"me"` or `null`. - A CSV filter contains an unknown value (`filter[status]`, `filter[review_status]`, `filter[event_type]`); the detail message lists the accepted values. - Batch `rows` is missing, empty, not an array of objects, or longer than 500; `execution_ids` is empty or longer than 25. - A record failed model validation on save — a cascade with no `name` or an unparseable `definition`, a webhook subscription with a blank, non-HTTPS, or private-network `url`, an unknown event in `event_filter`, a blank review `note`, or a blank / over-length `prompt` for generation. ## How to fix it 1. Read `details[]` — each entry names the `field` and the rule it broke. Fix every entry; the API reports all of them at once. 2. For filters, copy the allowed values from the detail message rather than guessing. 3. Split batches above 500 rows into several requests, each with its own `Idempotency-Key`. ## Operations that return it - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /batches](/docs/api/batches/list-batches) — List batches - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [GET /executions](/docs/api/executions/list-executions) — List executions - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries ## Example response ```http HTTP/1.1 422 Unprocessable Content Content-Type: application/json ``` ```json { "error": { "code": "validation_failed", "message": "The request could not be processed.", "details": [ { "field": "execution_mode", "message": "must be test or live" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/validation_failed" } } ``` ## Related - [/docs/concepts/pagination-and-filtering](/docs/concepts/pagination-and-filtering) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: missing_idempotency_key — Idempotency-Key header missing description: "HTTP 422 missing_idempotency_key: Idempotency-Key header is required." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/missing_idempotency_key" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 422 source: config/api_error_codes.yml --- # `missing_idempotency_key` — Idempotency-Key header missing **HTTP 422 Unprocessable Content** Message: "Idempotency-Key header is required." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - `POST /workflows/{id}/trigger`, `POST /workflows/{id}/batches`, or `POST /executions/{id}/re_evaluations` was sent without an `Idempotency-Key` header, or with an empty one. ## How to fix it 1. Send a unique string per logical request, up to 200 characters; a UUID v4 is the convention. Keep it stable across retries of the same request. ## Operations that return it - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution ## Example response ```http HTTP/1.1 422 Unprocessable Content Content-Type: application/json ``` ```json { "error": { "code": "missing_idempotency_key", "message": "Idempotency-Key header is required.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/missing_idempotency_key" } } ``` ## Related - [/docs/concepts/idempotency](/docs/concepts/idempotency) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: generation_failed — Cascade generation failed description: "HTTP 422 generation_failed: designer failure, e.g. \"We couldn't reach the cascade designer. Please try again.\"" canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/generation_failed" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 422 source: config/api_error_codes.yml --- # `generation_failed` — Cascade generation failed **HTTP 422 Unprocessable Content** Message: "" Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The designer rejected the prompt as off-topic or outside what a cascade can express (the message explains the rejection). - The generated definition failed validation even after the automatic repair pass; `details[]` lists the errors on `definition`. - No data products are enabled for the account, so there is nothing to ground a live cascade in. - The model could not be reached or returned an unexpected response. These are transient; the message says to try again. ## How to fix it 1. Reword the prompt to name the screening products and the decision rules explicitly; short, concrete prompts generate cleaner cascades. 2. Send `create: false` to preview the generated definition and warnings, adjust, then create it with `POST /workflows`. 3. Retry transient failures with backoff; if products are the problem, enable them on the account first. ## Operations that return it - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt ## Example response ```http HTTP/1.1 422 Unprocessable Content Content-Type: application/json ``` ```json { "error": { "code": "generation_failed", "message": "We couldn't reach the cascade designer. Please try again.", "details": [ { "field": "definition", "message": "node \"decision_1\" references unknown input \"credit_score\"" } ], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/generation_failed" } } ``` ## Related - [/docs/guides/ai-generate-a-cascade](/docs/guides/ai-generate-a-cascade) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: rate_limited — Rate limited description: "HTTP 429 rate_limited: Rate limit exceeded. Retry later." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/rate_limited" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 429 source: config/api_error_codes.yml --- # `rate_limited` — Rate limited **HTTP 429 Too Many Requests** Message: "Rate limit exceeded. Retry later." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - More than 60 requests per minute to `/api/v1` from one IP, more than 600 per hour for one API key, or more than 300 requests per 5 minutes to the whole app from one IP. - Fifteen or more unknown bearer tokens from one IP within five minutes — a 15-minute ban with the message "Too many failed authentication attempts. Retry later." ## How to fix it 1. Honour the `Retry-After` header (seconds) before resending; back off exponentially on repeated 429s. 2. Poll executions no faster than every few seconds, or subscribe to webhooks instead of polling. 3. Stop and fix credentials on the first 401 rather than retrying with variants — the failed-auth ban counts unknown tokens, not expired ones. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 429 Too Many Requests Content-Type: application/json Retry-After: 60 ``` ```json { "error": { "code": "rate_limited", "message": "Rate limit exceeded. Retry later.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/rate_limited" } } ``` ## Related - [/docs/concepts/scopes-and-keys](/docs/concepts/scopes-and-keys) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: internal_server_error — Internal server error description: "HTTP 500 internal_server_error: The request could not be completed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/internal_server_error" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 500 source: config/api_error_codes.yml --- # `internal_server_error` — Internal server error **HTTP 500 Internal Server Error** Message: "The request could not be completed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - An unexpected exception escaped the API. The response still carries a `request_id`, and the failure is logged and alerted on our side. ## How to fix it 1. Retry once after a short delay with the same `Idempotency-Key` where the operation accepts one; the retry is safe. 2. If it persists, contact support with the `request_id`, the operation, and the approximate time. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 500 Internal Server Error Content-Type: application/json ``` ```json { "error": { "code": "internal_server_error", "message": "The request could not be completed.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/internal_server_error" } } ``` [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: not_implemented — Not implemented description: "HTTP 501 not_implemented: The request could not be completed." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/not_implemented" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 501 source: config/api_error_codes.yml --- # `not_implemented` — Not implemented **HTTP 501 Not Implemented** Message: "The request could not be completed." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - The operation is declared but not available on this deployment. This comes from the framework with no `details`. ## How to fix it 1. Check the changelog for the operation's availability, or contact support with the `request_id`. ## Operations that return it Any operation can return this code. - [GET /workflows](/docs/api/workflows/list-workflows) — List workflows - [POST /workflows](/docs/api/workflows/create-workflow) — Create a workflow - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt - [GET /workflows/{id}](/docs/api/workflows/get-workflow) — Show a workflow - [PATCH /workflows/{id}](/docs/api/workflows/update-workflow) — Update a workflow - [DELETE /workflows/{id}](/docs/api/workflows/delete-workflow) — Delete a workflow - [GET /workflows/{id}/test_cases](/docs/api/workflows/list-workflow-test-cases) — List sandbox test cases for a workflow - [POST /workflows/{id}/trigger](/docs/api/workflows/trigger-workflow) — Trigger a workflow - [GET /workflow_templates](/docs/api/workflow-templates/list-workflow-templates) — List published templates - [GET /workflow_templates/{id}](/docs/api/workflow-templates/get-workflow-template) — Show a template (with definition) - [GET /executions](/docs/api/executions/list-executions) — List executions - [GET /executions/{id}](/docs/api/executions/get-execution) — Show an execution - [POST /executions/{id}/cancel](/docs/api/executions/cancel-execution) — Cancel an execution - [POST /executions/{id}/re_evaluations](/docs/api/executions/re-evaluate-execution) — Re-evaluate an execution - [POST /executions/{id}/review_decisions](/docs/api/executions/submit-review-decision) — Approve or deny a paused review - [GET /executions/{id}/review_events](/docs/api/executions/list-review-events) — List review audit events - [POST /executions/{id}/review_assignment](/docs/api/executions/assign-review) — Claim or unassign a review - [POST /executions/{id}/review_notes](/docs/api/executions/add-review-note) — Append a review note - [POST /executions/review_bulk_actions](/docs/api/executions/bulk-review-actions) — Bulk approve / deny / assign_to_me - [GET /executions/{id}/report_artifacts](/docs/api/executions/list-report-artifacts) — List credit-report PDFs for an execution - [GET /executions/{id}/report_artifacts/{artifact_id}](/docs/api/executions/get-report-artifact) — Download a credit-report PDF - [GET /executions/{id}/notice_artifacts](/docs/api/executions/list-notice-artifacts) — List adverse-action notice PDFs for an execution - [GET /executions/{id}/notice_artifacts/{artifact_id}](/docs/api/executions/get-notice-artifact) — Download an adverse-action notice PDF - [POST /workflows/{id}/batches](/docs/api/batches/create-batch) — Create a batch from N rows - [GET /batches](/docs/api/batches/list-batches) — List batches - [GET /batches/{id}](/docs/api/batches/get-batch) — Show a batch - [GET /batches/{id}/executions](/docs/api/batches/list-batch-executions) — List child executions of a batch - [POST /batches/{id}/cancel](/docs/api/batches/cancel-batch) — Cancel a batch - [POST /batches/{id}/resume](/docs/api/batches/resume-batch) — Resume an interrupted batch - [POST /batches/{id}/retry_failed](/docs/api/batches/retry-failed-batch) — Retry failed and skipped rows - [GET /batches/{id}/exports.csv](/docs/api/batches/export-batch) — Download flat results CSV - [GET /batches/{id}/exports.zip](/docs/api/batches/export-batch-zip) — Download structured ZIP of batch results - [GET /batches/{id}/adverse_action_notices.zip](/docs/api/batches/download-batch-adverse-action-notices) — Download all adverse-action notice PDFs as a ZIP - [GET /webhook_subscriptions](/docs/api/webhooks/list-webhook-subscriptions) — List webhook subscriptions - [POST /webhook_subscriptions](/docs/api/webhooks/create-webhook-subscription) — Create a webhook subscription - [GET /webhook_subscriptions/{id}](/docs/api/webhooks/get-webhook-subscription) — Show a webhook subscription - [PATCH /webhook_subscriptions/{id}](/docs/api/webhooks/update-webhook-subscription) — Update a webhook subscription - [DELETE /webhook_subscriptions/{id}](/docs/api/webhooks/delete-webhook-subscription) — Delete a webhook subscription - [POST /webhook_subscriptions/{id}/rotate_secret](/docs/api/webhooks/rotate-webhook-secret) — Rotate the signing secret - [GET /webhook_deliveries](/docs/api/webhooks/list-webhook-deliveries) — List webhook deliveries - [GET /webhook_deliveries/{id}](/docs/api/webhooks/get-webhook-delivery) — Show a webhook delivery - [POST /webhook_deliveries/{id}/retry](/docs/api/webhooks/retry-webhook-delivery) — Manually retry a failed or dead delivery ## Example response ```http HTTP/1.1 501 Not Implemented Content-Type: application/json ``` ```json { "error": { "code": "not_implemented", "message": "The request could not be completed.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/not_implemented" } } ``` ## Related - [/docs/changelog](/docs/changelog) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: designer_busy — Cascade designer busy description: "HTTP 503 designer_busy: The cascade designer is busy right now. Please try again in a moment." canonical_url: "https://cascades.crscreditapi.com/docs/api/errors/designer_busy" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" http_status: 503 source: config/api_error_codes.yml --- # `designer_busy` — Cascade designer busy **HTTP 503 Service Unavailable** Message: "The cascade designer is busy right now. Please try again in a moment." Envelope schema: `ErrorEnvelope` in [openapi.json](https://cascades.crscreditapi.com/openapi.json#/components/schemas/ErrorEnvelope) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). ## What causes it - Every concurrent generation slot for the AI designer is in use (shared with the in-app builder). The request was not started. ## How to fix it 1. Retry after a few seconds with backoff; generation requests are not idempotent, so do not queue duplicates. 2. Author the definition directly with `POST /workflows` when you already know the shape you need. ## Operations that return it - [POST /workflows/generate](/docs/api/workflows/generate-workflow) — Generate a workflow from a prompt ## Example response ```http HTTP/1.1 503 Service Unavailable Content-Type: application/json ``` ```json { "error": { "code": "designer_busy", "message": "The cascade designer is busy right now. Please try again in a moment.", "details": [], "request_id": "6f1c2a3e-8d4b-4c5e-9f0a-1b2c3d4e5f60", "doc_url": "https://cascades.crscreditapi.com/docs/api/errors/designer_busy" } } ``` ## Related - [/docs/guides/ai-generate-a-cascade](/docs/guides/ai-generate-a-cascade) [All error codes](https://cascades.crscreditapi.com/docs/api/errors.md) --- --- title: Webhook events description: Every event Cascades delivers, the X-Cascades-Signature scheme, and the retry schedule. canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" --- # Webhook events Every delivery is an HTTPS POST signed with `X-Cascades-Signature: t=,v1=.">`. Retries: 1m, 5m, 30m, 2h, 8h, 24h, then `dead`. Payload schemas are the `x-webhooks` entries of the OpenAPI document: https://cascades.crscreditapi.com/openapi.json · https://cascades.crscreditapi.com/openapi.yaml. | Event | Summary | |---|---| | [`execution.completed`](https://cascades.crscreditapi.com/docs/api/webhooks/execution.completed.md) | An execution reached `completed` | | [`execution.failed`](https://cascades.crscreditapi.com/docs/api/webhooks/execution.failed.md) | An execution reached `failed` | | [`adverse_action.triggered`](https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.triggered.md) | An adverse-action notice was sent (or the pre-adverse waiting period started) | | [`adverse_action.completed`](https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.completed.md) | The adverse-action flow finished | | [`adverse_action.failed`](https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.failed.md) | A notice could not be delivered | | [`adverse_action.retry_scheduled`](https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.retry_scheduled.md) | A post-adverse notice delivery failed and a retry was scheduled | ```json { "id": "6d1f1a5e-4a2b-4c7d-9e8f-0a1b2c3d4e5f", "event": "execution.completed", "created_at": "2026-09-09T14:03:21Z", "data": { "workflow_execution_id": 1842, "workflow_id": 27, "external_reference": "rv-app-20491", "execution_mode": "test", "status": "completed", "review_status": "none", "completed_at": "2026-09-09T14:03:20Z" } } ``` ## Ruby ```ruby require "openssl" def verify_cascades_signature(header, raw_body, secret, tolerance: 60) parts = header.to_s.split(",").map { |p| p.split("=", 2) }.to_h ts = parts["t"].to_i return false if ts == 0 return false if (Time.now.to_i - ts).abs > tolerance expected = OpenSSL::HMAC.hexdigest("SHA256", secret, "#{ts}.#{raw_body}") parts.any? do |k, v| k.start_with?("v") && OpenSSL.secure_compare(expected, v) end end ``` ## Python ```python import hmac import hashlib import time def verify_cascades_signature(header, raw_body, secret, tolerance=60): parts = dict(p.split("=", 1) for p in header.split(",")) ts = int(parts.get("t", 0)) if ts == 0 or abs(int(time.time()) - ts) > tolerance: return False signed = f"{ts}.{raw_body}".encode() expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest() return any( k.startswith("v") and hmac.compare_digest(expected, v) for k, v in parts.items() ) ``` ## Node.js ```javascript const crypto = require("crypto"); function verifyCascadesSignature(header, rawBody, secret, tolerance = 60) { const parts = Object.fromEntries(header.split(",").map(p => p.split("="))); const ts = parseInt(parts.t, 10); if (!ts) return false; if (Math.abs(Math.floor(Date.now() / 1000) - ts) > tolerance) return false; const expected = crypto .createHmac("sha256", secret) .update(`${ts}.${rawBody}`) .digest("hex"); const expBuf = Buffer.from(expected, "hex"); return Object.entries(parts).some(([k, v]) => { if (!k.startsWith("v")) return false; const sigBuf = Buffer.from(v, "hex"); return sigBuf.length === expBuf.length && crypto.timingSafeEqual(expBuf, sigBuf); }); } ``` --- --- title: execution.completed webhook description: Sent once when an execution's status transitions to completed. Emitted for API-triggered runs only (executions started from the Cascades UI have no… canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/execution.completed" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/x-webhooks/execution.completed" --- # `execution.completed` An execution reached `completed` Sent once when an execution's status transitions to `completed`. Emitted for API-triggered runs only (executions started from the Cascades UI have no api_client and produce no webhooks). Poll `GET /executions/{workflow_execution_id}` for the decision and `step_outputs`. Payload schema: [openapi.json](https://cascades.crscreditapi.com/openapi.json#/x-webhooks/execution.completed) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). Deliveries are signed; see [Webhook events](https://cascades.crscreditapi.com/docs/api/webhooks.md) for the `X-Cascades-Signature` scheme. | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | | | `event` | string | yes | One of: `"execution.completed"`. | | `created_at` | string (date-time) | yes | | | `data` | object | yes | | |   `workflow_execution_id` | integer | yes | | |   `workflow_id` | integer | yes | | |   `batch_execution_id` | integer | no | Present only for batch rows. | |   `external_reference` | string | no | Present when the run was triggered with one. | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `status` | string | yes | One of: `"completed"`. | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `completed_at` | string (date-time) | no | | ```json { "id": "6d1f1a5e-4a2b-4c7d-9e8f-0a1b2c3d4e5f", "event": "execution.completed", "created_at": "2026-09-09T14:03:21Z", "data": { "workflow_execution_id": 1842, "workflow_id": 27, "external_reference": "rv-app-20491", "execution_mode": "test", "status": "completed", "review_status": "none", "completed_at": "2026-09-09T14:03:20Z" } } ``` --- --- title: execution.failed webhook description: Sent once when an execution's status transitions to failed. first_error_message is the engine's first recorded error; the full failed_steps array is… canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/execution.failed" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/x-webhooks/execution.failed" --- # `execution.failed` An execution reached `failed` Sent once when an execution's status transitions to `failed`. `first_error_message` is the engine's first recorded error; the full `failed_steps` array is available from `GET /executions/{workflow_execution_id}` with `executions:read:full`. Payload schema: [openapi.json](https://cascades.crscreditapi.com/openapi.json#/x-webhooks/execution.failed) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). Deliveries are signed; see [Webhook events](https://cascades.crscreditapi.com/docs/api/webhooks.md) for the `X-Cascades-Signature` scheme. | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | | | `event` | string | yes | One of: `"execution.failed"`. | | `created_at` | string (date-time) | yes | | | `data` | object | yes | | |   `workflow_execution_id` | integer | yes | | |   `workflow_id` | integer | yes | | |   `batch_execution_id` | integer | no | Present only for batch rows. | |   `external_reference` | string | no | | |   `execution_mode` | string | no | One of: `"test"`, `"live"`. | |   `status` | string | yes | One of: `"failed"`. | |   `review_status` | string | yes | One of: `"none"`, `"in_review"`, `"approved"`, `"denied"`. | |   `completed_at` | string (date-time) | no | | |   `first_error_message` | string | no | The first error the engine recorded for this run. | ```json { "id": "9b2e7c10-3d4f-4a5b-8c6d-7e8f9a0b1c2d", "event": "execution.failed", "created_at": "2026-09-09T14:05:02Z", "data": { "workflow_execution_id": 1843, "workflow_id": 27, "external_reference": "rv-app-20492", "execution_mode": "live", "status": "failed", "review_status": "none", "completed_at": "2026-09-09T14:05:01Z", "first_error_message": "Equifax: subject not found" } } ``` --- --- title: adverse_action.triggered webhook description: "Emitted by the AdverseAction node when a notice goes out. For the two-step employment flow (notice_stage: pre_adverse) the execution then enters waiting…" canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.triggered" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/x-webhooks/adverse_action.triggered" --- # `adverse_action.triggered` An adverse-action notice was sent (or the pre-adverse waiting period started) Emitted by the `AdverseAction` node when a notice goes out. For the two-step employment flow (`notice_stage: pre_adverse`) the execution then enters `waiting` until `resume_at`, when the post-adverse notice is sent. For single-notice flows (`notice_stage: final`) it is immediately followed by `adverse_action.completed`. Payload schema: [openapi.json](https://cascades.crscreditapi.com/openapi.json#/x-webhooks/adverse_action.triggered) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). Deliveries are signed; see [Webhook events](https://cascades.crscreditapi.com/docs/api/webhooks.md) for the `X-Cascades-Signature` scheme. | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | | | `event` | string | yes | One of: `"adverse_action.triggered"`. | | `created_at` | string (date-time) | yes | | | `data` | object | yes | | |   `workflow_execution_id` | integer | yes | | |   `workflow_id` | integer | yes | | |   `node_id` | string | yes | The `AdverseAction` node's id in the definition. | |   `external_reference` | string | no | | |   `flow` | string | yes | `employment` for the two-step pre/post flow; otherwise the workflow's use case (e.g. `tenant_screening`). | |   `notice_stage` | string | yes | One of: `"pre_adverse"`, `"final"`. | |   `sent_to` | string | no | Recipient address the notice was sent to. | |   `delivery_channels` | array of string | no | `final` stage only: the channels used. Currently always `digital` (physical mail is not yet enabled). | |   `triggered_at` | string (date-time) | no | | |   `resume_at` | string (date-time) | no | `pre_adverse` only: when the post-adverse notice will be sent. | |   `status` | string | yes | One of: `"waiting"`, `"completed"`. | ```json { "id": "1c9d8e7f-6a5b-4c3d-2e1f-0a9b8c7d6e5f", "event": "adverse_action.triggered", "created_at": "2026-09-09T14:06:40Z", "data": { "workflow_execution_id": 1844, "workflow_id": 27, "node_id": "step_adverse_action", "external_reference": "rv-app-20493", "flow": "tenant_screening", "notice_stage": "final", "sent_to": "jordan@example.com", "delivery_channels": [ "digital" ], "triggered_at": "2026-09-09T14:06:39Z", "status": "completed" } } ``` --- --- title: adverse_action.completed webhook description: Emitted when the final (or post-adverse) notice has been delivered. The notice PDF is listed by GET /executions/{workflow_execution_id}/notice_artifacts. canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.completed" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/x-webhooks/adverse_action.completed" --- # `adverse_action.completed` The adverse-action flow finished Emitted when the final (or post-adverse) notice has been delivered. The notice PDF is listed by `GET /executions/{workflow_execution_id}/notice_artifacts`. Payload schema: [openapi.json](https://cascades.crscreditapi.com/openapi.json#/x-webhooks/adverse_action.completed) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). Deliveries are signed; see [Webhook events](https://cascades.crscreditapi.com/docs/api/webhooks.md) for the `X-Cascades-Signature` scheme. | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | | | `event` | string | yes | One of: `"adverse_action.completed"`. | | `created_at` | string (date-time) | yes | | | `data` | object | yes | | |   `workflow_execution_id` | integer | yes | | |   `workflow_id` | integer | yes | | |   `node_id` | string | yes | | |   `external_reference` | string | no | | |   `flow` | string | yes | | |   `notice_stage` | string | yes | One of: `"post_adverse"`, `"final"`. | |   `sent_to` | string | no | | |   `delivery_channels` | array of string | no | | |   `completed_at` | string (date-time) | no | | |   `status` | string | yes | One of: `"completed"`. | ```json { "id": "2d0e9f8a-7b6c-4d5e-3f2a-1b0c9d8e7f6a", "event": "adverse_action.completed", "created_at": "2026-09-09T14:06:41Z", "data": { "workflow_execution_id": 1844, "workflow_id": 27, "node_id": "step_adverse_action", "external_reference": "rv-app-20493", "flow": "tenant_screening", "notice_stage": "final", "sent_to": "jordan@example.com", "delivery_channels": [ "digital" ], "completed_at": "2026-09-09T14:06:39Z", "status": "completed" } } ``` --- --- title: adverse_action.failed webhook description: Emitted when sending a notice raised an error. For the post-adverse stage this fires only after the retry budget is exhausted (see… canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.failed" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/x-webhooks/adverse_action.failed" --- # `adverse_action.failed` A notice could not be delivered Emitted when sending a notice raised an error. For the post-adverse stage this fires only after the retry budget is exhausted (see `adverse_action.retry_scheduled` for the intermediate attempts); the execution then fails. Payload schema: [openapi.json](https://cascades.crscreditapi.com/openapi.json#/x-webhooks/adverse_action.failed) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). Deliveries are signed; see [Webhook events](https://cascades.crscreditapi.com/docs/api/webhooks.md) for the `X-Cascades-Signature` scheme. | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | | | `event` | string | yes | One of: `"adverse_action.failed"`. | | `created_at` | string (date-time) | yes | | | `data` | object | yes | | |   `workflow_execution_id` | integer | yes | | |   `workflow_id` | integer | yes | | |   `node_id` | string | yes | | |   `external_reference` | string | no | | |   `flow` | string | yes | | |   `notice_stage` | string | yes | One of: `"pre_adverse"`, `"post_adverse"`, `"final"`. | |   `error` | string | yes | Human-readable delivery error. | |   `failed_at` | string (date-time) | no | | |   `attempt` | integer | no | `post_adverse` only: the attempt number that exhausted the retry budget. | |   `status` | string | yes | One of: `"failed"`. | ```json { "id": "3e1f0a9b-8c7d-4e6f-5a4b-2c1d0e9f8a7b", "event": "adverse_action.failed", "created_at": "2026-09-09T14:08:12Z", "data": { "workflow_execution_id": 1845, "workflow_id": 27, "node_id": "step_adverse_action", "flow": "tenant_screening", "notice_stage": "final", "error": "Failed to send adverse action notice to jordan@example.com: SMTP 550 mailbox unavailable", "failed_at": "2026-09-09T14:08:11Z", "status": "failed" } } ``` --- --- title: adverse_action.retry_scheduled webhook description: Employment (two-step) flow only. The post-adverse notice could not be delivered; Cascades scheduled another attempt at retry_at with exponential backoff.… canonical_url: "https://cascades.crscreditapi.com/docs/api/webhooks/adverse_action.retry_scheduled" doc_version: "1.0.0" last_updated: "2026-10-09T18:04:24Z" source: "public/api-docs/v1/swagger.yaml#/x-webhooks/adverse_action.retry_scheduled" --- # `adverse_action.retry_scheduled` A post-adverse notice delivery failed and a retry was scheduled Employment (two-step) flow only. The post-adverse notice could not be delivered; Cascades scheduled another attempt at `retry_at` with exponential backoff. `adverse_action.failed` follows if the retry budget runs out. Payload schema: [openapi.json](https://cascades.crscreditapi.com/openapi.json#/x-webhooks/adverse_action.retry_scheduled) ([YAML](https://cascades.crscreditapi.com/openapi.yaml)). Deliveries are signed; see [Webhook events](https://cascades.crscreditapi.com/docs/api/webhooks.md) for the `X-Cascades-Signature` scheme. | Field | Type | Required | Description | |---|---|---|---| | `id` | string (uuid) | yes | | | `event` | string | yes | One of: `"adverse_action.retry_scheduled"`. | | `created_at` | string (date-time) | yes | | | `data` | object | yes | | |   `workflow_execution_id` | integer | yes | | |   `workflow_id` | integer | yes | | |   `node_id` | string | yes | | |   `external_reference` | string | no | | |   `flow` | string | yes | One of: `"employment"`. | |   `notice_stage` | string | yes | One of: `"post_adverse"`. | |   `error` | string | yes | | |   `attempt` | integer | yes | The attempt that just failed (1-based). | |   `next_attempt` | integer | yes | | |   `retry_at` | string (date-time) | yes | | |   `wait_id` | integer | no | Id of the internal wait record backing the retry. | ```json { "id": "4f2a1b0c-9d8e-4f7a-6b5c-3d2e1f0a9b8c", "event": "adverse_action.retry_scheduled", "created_at": "2026-09-09T14:09:00Z", "data": { "workflow_execution_id": 1846, "workflow_id": 31, "node_id": "step_adverse_action", "flow": "employment", "notice_stage": "post_adverse", "error": "SMTP connection timed out", "attempt": 1, "next_attempt": 2, "retry_at": "2026-09-09T14:39:00Z", "wait_id": 9021 } } ``` --- --- title: MCP server description: Install the Cascades MCP server (@crscreditapi/cascades-mcp-server) in Claude Code, Claude Desktop, Cursor, VS Code or Windsurf and run cascades from your… canonical_url: "https://cascades.crscreditapi.com/docs/mcp" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # Cascades MCP server `@crscreditapi/cascades-mcp-server` is a Model Context Protocol server that exposes the Cascades API as 9 tools. Every tool is exactly one authenticated REST call using your own API key and scopes — no separate MCP auth. ## Install - [Claude Code](https://cascades.crscreditapi.com/docs/mcp/install/claude-code.md) - [Claude Desktop](https://cascades.crscreditapi.com/docs/mcp/install/claude-desktop.md) - [Cursor](https://cascades.crscreditapi.com/docs/mcp/install/cursor.md) - [VS Code](https://cascades.crscreditapi.com/docs/mcp/install/vscode.md) - [Windsurf](https://cascades.crscreditapi.com/docs/mcp/install/windsurf.md) - [ChatGPT](https://cascades.crscreditapi.com/docs/mcp/install/chatgpt.md) ## Requirements - Node.js 18 or newer (clients run `npx -y @crscreditapi/cascades-mcp-server`). - An API key with `workflows:read`, `workflows:write`, `executions:read`, `executions:write`. A key missing a scope gets `403 insufficient_scope` on the affected tools only. - `CASCADES_PROD_BASE_URL` is the host **without** `/api/v1`: `https://cascades.crscreditapi.com`. ## Security - **Live runs are billable and blocked for docs keys.** `cascades_trigger_workflow` defaults to `execution_mode: "test"`; `"live"` pulls real reports, needs a production-approved account, and is rejected for sandbox and docs-issued keys. - **Delete asks for confirmation.** `cascades_delete_workflow` soft-deletes a cascade; its description tells the model to confirm first. Use a read-only key (no `:write` scopes) for agents that should never mutate anything. - **Never expose HTTP mode without auth.** `MCP_TRANSPORT=http` serves every caller with one process-level key and has no per-request auth. Keep it on a private network; use stdio for people. ## Tools | Tool | Access | REST call | Scopes | |---|---|---|---| | [`cascades_create_workflow`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_create_workflow.md) | write | `POST /workflows` | `workflows:write` | | [`cascades_delete_workflow`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_delete_workflow.md) | destructive | `DELETE /workflows/{id}` | `workflows:write` | | [`cascades_generate_workflow`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_generate_workflow.md) | write | `POST /workflows/generate` | `workflows:write` | | [`cascades_get_execution`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_get_execution.md) | read | `GET /executions/{id}` | `executions:read` | | [`cascades_get_workflow`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_get_workflow.md) | read | `GET /workflows/{id}` | `workflows:read` | | [`cascades_list_test_cases`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_list_test_cases.md) | read | `GET /workflows/{id}/test_cases` | `workflows:read` | | [`cascades_list_workflows`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_list_workflows.md) | read | `GET /workflows` | `workflows:read` | | [`cascades_trigger_workflow`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_trigger_workflow.md) | write | `POST /workflows/{id}/trigger` | `executions:write` | | [`cascades_update_workflow`](https://cascades.crscreditapi.com/docs/mcp/tools/cascades_update_workflow.md) | write | `PATCH /workflows/{id}` | `workflows:write` | ## A worked session **You:** List my cascades and run the standard tenant screening for one of the sandbox test cases. **Tool call** `cascades_list_workflows` ```json { "active": true } ``` **Result** ```json { "data": [ { "id": 157, "object": "workflow", "code": "WF-157", "name": "Tenant screening — standard", "use_case": "tenant_screening", "trigger_type": "manual", "active": true } ], "meta": { "page": 1, "per_page": 25, "total_count": 1, "total_pages": 1, "object": "list" } } ``` **Assistant:** You have one active cascade: **Tenant screening — standard** (id 157). Looking up its registered test subjects. **Tool call** `cascades_list_test_cases` ```json { "workflow_id": 157 } ``` **Result** ```json { "object": "test_case_list", "workflow_id": 157, "test_cases": [ { "id": 54, "case_name": "Cascades_Credit_720", "product_code": "TUC1001", "payload": { "applicant": { "first_name": "Sandrx", "last_name": "Bowkey" }, "property": { "state": "MD" } } } ] } ``` **Assistant:** One subject is registered for its TransUnion credit pull: *Cascades_Credit_720* (Sandrx Bowkey). Running it in test mode. **Tool call** `cascades_trigger_workflow` ```json { "workflow_id": 157, "execution_mode": "test", "payload": { "applicant": { "first_name": "Sandrx", "last_name": "Bowkey" }, "property": { "state": "MD" } } } ``` **Result** ```json { "data": { "id": 4821, "object": "execution", "workflow_id": 157, "status": "pending", "execution_mode": "test", "created_at": "2026-09-09T17:04:11Z" } } ``` **Assistant:** Execution 4821 is pending — the run is asynchronous. Polling until it reaches a terminal status. **Tool call** `cascades_get_execution` ```json { "execution_id": 4821 } ``` **Result** ```json { "data": { "id": 4821, "object": "execution", "workflow_id": 157, "status": "completed", "decision": "approve", "step_outputs": { "credit": { "score": 712 }, "decision": { "outcome": "approve" } } } } ``` **Assistant:** Done: execution 4821 **completed** with decision **approve**. The credit step returned a 712 score, above the cascade's 680 threshold, and no other rule fired. ## Test with MCP Inspector ```bash CASCADES_API_KEY={{API_KEY}} CASCADES_PROD_BASE_URL=https://cascades.crscreditapi.com \ npx @modelcontextprotocol/inspector npx -y @crscreditapi/cascades-mcp-server ``` ## Configuration | Variable | Required | Description | |---|---|---| | `CASCADES_API_KEY` | yes | Bearer token, sent as `Authorization: Bearer …` on every call. | | `CASCADES_PROD_BASE_URL` | yes | Cascades host without `/api/v1`. | | `CASCADES_DEV_BASE_URL` | no | Alternate host for `environment: "dev"`; falls back to prod. | | `CASCADES_TIMEOUT_MS` | no | Per-request timeout, default `30000`. | | `MCP_TRANSPORT` | no | `stdio` (default) or `http`. | | `MCP_PORT` | no | HTTP-mode port, default `3004`. | --- --- title: Add Cascades to Claude Code description: "Add Cascades to Claude Code: config, install command and the scopes your API key needs for the Cascades MCP server." canonical_url: "https://cascades.crscreditapi.com/docs/mcp/install/claude-code" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # Add Cascades to Claude Code 1. Run the command below from your project. `npx` fetches the server on first use; nothing to install. 2. Restart Claude Code (or run `/mcp`) and confirm `cascades` shows as connected. 3. Ask: *List my cascades and run the first one for a sandbox test case.* ```bash 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 ``` `.mcp.json`: ```json { "mcpServers": { "cascades": { "command": "npx", "args": [ "-y", "@crscreditapi/cascades-mcp-server" ], "env": { "CASCADES_API_KEY": "{{API_KEY}}", "CASCADES_PROD_BASE_URL": "https://cascades.crscreditapi.com" } } } } ``` ## Notes - `CASCADES_PROD_BASE_URL` is the host **without** `/api/v1` (`https://cascades.crscreditapi.com`); the server adds the prefix. - The key needs `workflows:read`, `workflows:write`, `executions:read`, `executions:write`. A key missing a scope gets `403 insufficient_scope` on the affected tool only. - Sandbox keys can only run in test mode: `execution_mode: "live"` is billable, needs a production-approved account, and is rejected for docs-issued keys. - Prefer the JSON? Commit `.mcp.json` to the repo so every collaborator gets the server; keep the key out of git by using `${CASCADES_API_KEY}` and exporting it in your shell. ## Test with MCP Inspector ```bash CASCADES_API_KEY={{API_KEY}} CASCADES_PROD_BASE_URL=https://cascades.crscreditapi.com \ npx @modelcontextprotocol/inspector npx -y @crscreditapi/cascades-mcp-server ``` ## Other clients - [Claude Desktop](https://cascades.crscreditapi.com/docs/mcp/install/claude-desktop.md) - [Cursor](https://cascades.crscreditapi.com/docs/mcp/install/cursor.md) - [VS Code](https://cascades.crscreditapi.com/docs/mcp/install/vscode.md) - [Windsurf](https://cascades.crscreditapi.com/docs/mcp/install/windsurf.md) - [ChatGPT](https://cascades.crscreditapi.com/docs/mcp/install/chatgpt.md) --- --- title: Add Cascades to Claude Desktop description: "Add Cascades to Claude Desktop: config, install command and the scopes your API key needs for the Cascades MCP server." canonical_url: "https://cascades.crscreditapi.com/docs/mcp/install/claude-desktop" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # Add Cascades to Claude Desktop 1. Open Claude Desktop → Settings → Developer → *Edit Config*. That opens `claude_desktop_config.json` (macOS: `~/Library/Application Support/Claude/`, Windows: `%APPDATA%\Claude\`). 2. Merge the JSON below into the file (keep any other servers you already have). 3. Quit and reopen Claude Desktop. The hammer icon lists the nine `cascades_*` tools. `claude_desktop_config.json`: ```json { "mcpServers": { "cascades": { "command": "npx", "args": [ "-y", "@crscreditapi/cascades-mcp-server" ], "env": { "CASCADES_API_KEY": "{{API_KEY}}", "CASCADES_PROD_BASE_URL": "https://cascades.crscreditapi.com" } } } } ``` ## Notes - `CASCADES_PROD_BASE_URL` is the host **without** `/api/v1` (`https://cascades.crscreditapi.com`); the server adds the prefix. - The key needs `workflows:read`, `workflows:write`, `executions:read`, `executions:write`. A key missing a scope gets `403 insufficient_scope` on the affected tool only. - Sandbox keys can only run in test mode: `execution_mode: "live"` is billable, needs a production-approved account, and is rejected for docs-issued keys. - Claude Desktop launches the server with its own PATH; if `npx` is not found, replace `"command": "npx"` with the absolute path from `which npx`. ## Test with MCP Inspector ```bash CASCADES_API_KEY={{API_KEY}} CASCADES_PROD_BASE_URL=https://cascades.crscreditapi.com \ npx @modelcontextprotocol/inspector npx -y @crscreditapi/cascades-mcp-server ``` ## Other clients - [Claude Code](https://cascades.crscreditapi.com/docs/mcp/install/claude-code.md) - [Cursor](https://cascades.crscreditapi.com/docs/mcp/install/cursor.md) - [VS Code](https://cascades.crscreditapi.com/docs/mcp/install/vscode.md) - [Windsurf](https://cascades.crscreditapi.com/docs/mcp/install/windsurf.md) - [ChatGPT](https://cascades.crscreditapi.com/docs/mcp/install/chatgpt.md) --- --- title: Add Cascades to Cursor description: "Add Cascades to Cursor: config, install command and the scopes your API key needs for the Cascades MCP server." canonical_url: "https://cascades.crscreditapi.com/docs/mcp/install/cursor" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # Add Cascades to Cursor 1. Click *Add to Cursor* (one-click install; your key is filled in when you are signed in), or 2. create `.cursor/mcp.json` in your project (or `~/.cursor/mcp.json` globally) with the JSON below. 3. Open Cursor Settings → MCP and enable `cascades`. The tools appear in Agent mode. `.cursor/mcp.json`: ```json { "mcpServers": { "cascades": { "command": "npx", "args": [ "-y", "@crscreditapi/cascades-mcp-server" ], "env": { "CASCADES_API_KEY": "{{API_KEY}}", "CASCADES_PROD_BASE_URL": "https://cascades.crscreditapi.com" } } } } ``` One-click install (replace `{{API_KEY}}` with your key before following it): ``` cursor://anysphere.cursor-deeplink/mcp/install?name=cascades&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjcnNjcmVkaXRhcGkvY2FzY2FkZXMtbWNwLXNlcnZlciJdLCJlbnYiOnsiQ0FTQ0FERVNfQVBJX0tFWSI6Int7QVBJX0tFWX19IiwiQ0FTQ0FERVNfUFJPRF9CQVNFX1VSTCI6Imh0dHBzOi8vY2FzY2FkZXMuY3JzY3JlZGl0YXBpLmNvbSJ9fQ== ``` ## Notes - `CASCADES_PROD_BASE_URL` is the host **without** `/api/v1` (`https://cascades.crscreditapi.com`); the server adds the prefix. - The key needs `workflows:read`, `workflows:write`, `executions:read`, `executions:write`. A key missing a scope gets `403 insufficient_scope` on the affected tool only. - Sandbox keys can only run in test mode: `execution_mode: "live"` is billable, needs a production-approved account, and is rejected for docs-issued keys. ## Test with MCP Inspector ```bash CASCADES_API_KEY={{API_KEY}} CASCADES_PROD_BASE_URL=https://cascades.crscreditapi.com \ npx @modelcontextprotocol/inspector npx -y @crscreditapi/cascades-mcp-server ``` ## Other clients - [Claude Code](https://cascades.crscreditapi.com/docs/mcp/install/claude-code.md) - [Claude Desktop](https://cascades.crscreditapi.com/docs/mcp/install/claude-desktop.md) - [VS Code](https://cascades.crscreditapi.com/docs/mcp/install/vscode.md) - [Windsurf](https://cascades.crscreditapi.com/docs/mcp/install/windsurf.md) - [ChatGPT](https://cascades.crscreditapi.com/docs/mcp/install/chatgpt.md) --- --- title: Add Cascades to VS Code description: "Add Cascades to VS Code: config, install command and the scopes your API key needs for the Cascades MCP server." canonical_url: "https://cascades.crscreditapi.com/docs/mcp/install/vscode" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # Add Cascades to VS Code 1. Click *Add to VS Code* (needs VS Code 1.99+ with GitHub Copilot agent mode), or 2. create `.vscode/mcp.json` in your workspace with the JSON below. VS Code's schema is `servers`, not `mcpServers`, and declares the transport explicitly. 3. Run *MCP: List Servers* from the command palette and start `cascades`. `.vscode/mcp.json`: ```json { "servers": { "cascades": { "type": "stdio", "command": "npx", "args": [ "-y", "@crscreditapi/cascades-mcp-server" ], "env": { "CASCADES_API_KEY": "{{API_KEY}}", "CASCADES_PROD_BASE_URL": "https://cascades.crscreditapi.com" } } } } ``` One-click install (replace `{{API_KEY}}` with your key before following it): ``` vscode:mcp/install?%7B%22name%22%3A%22cascades%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40crscreditapi%2Fcascades-mcp-server%22%5D%2C%22env%22%3A%7B%22CASCADES_API_KEY%22%3A%22%7B%7BAPI_KEY%7D%7D%22%2C%22CASCADES_PROD_BASE_URL%22%3A%22https%3A%2F%2Fcascades.crscreditapi.com%22%7D%7D ``` ## Notes - `CASCADES_PROD_BASE_URL` is the host **without** `/api/v1` (`https://cascades.crscreditapi.com`); the server adds the prefix. - The key needs `workflows:read`, `workflows:write`, `executions:read`, `executions:write`. A key missing a scope gets `403 insufficient_scope` on the affected tool only. - Sandbox keys can only run in test mode: `execution_mode: "live"` is billable, needs a production-approved account, and is rejected for docs-issued keys. - To keep the key out of the workspace file, VS Code supports `"inputs"` with `"password": true` and `${input:cascades_api_key}` in `env`. ## Test with MCP Inspector ```bash CASCADES_API_KEY={{API_KEY}} CASCADES_PROD_BASE_URL=https://cascades.crscreditapi.com \ npx @modelcontextprotocol/inspector npx -y @crscreditapi/cascades-mcp-server ``` ## Other clients - [Claude Code](https://cascades.crscreditapi.com/docs/mcp/install/claude-code.md) - [Claude Desktop](https://cascades.crscreditapi.com/docs/mcp/install/claude-desktop.md) - [Cursor](https://cascades.crscreditapi.com/docs/mcp/install/cursor.md) - [Windsurf](https://cascades.crscreditapi.com/docs/mcp/install/windsurf.md) - [ChatGPT](https://cascades.crscreditapi.com/docs/mcp/install/chatgpt.md) --- --- title: Add Cascades to Windsurf description: "Add Cascades to Windsurf: config, install command and the scopes your API key needs for the Cascades MCP server." canonical_url: "https://cascades.crscreditapi.com/docs/mcp/install/windsurf" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # Add Cascades to Windsurf 1. Open Windsurf → Cascade panel → *MCP servers* → *Configure*, which opens `~/.codeium/windsurf/mcp_config.json`. 2. Merge the JSON below into the file. 3. Press *Refresh* in the MCP panel; the `cascades_*` tools are available to Cascade. `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "cascades": { "command": "npx", "args": [ "-y", "@crscreditapi/cascades-mcp-server" ], "env": { "CASCADES_API_KEY": "{{API_KEY}}", "CASCADES_PROD_BASE_URL": "https://cascades.crscreditapi.com" } } } } ``` ## Notes - `CASCADES_PROD_BASE_URL` is the host **without** `/api/v1` (`https://cascades.crscreditapi.com`); the server adds the prefix. - The key needs `workflows:read`, `workflows:write`, `executions:read`, `executions:write`. A key missing a scope gets `403 insufficient_scope` on the affected tool only. - Sandbox keys can only run in test mode: `execution_mode: "live"` is billable, needs a production-approved account, and is rejected for docs-issued keys. ## Test with MCP Inspector ```bash CASCADES_API_KEY={{API_KEY}} CASCADES_PROD_BASE_URL=https://cascades.crscreditapi.com \ npx @modelcontextprotocol/inspector npx -y @crscreditapi/cascades-mcp-server ``` ## Other clients - [Claude Code](https://cascades.crscreditapi.com/docs/mcp/install/claude-code.md) - [Claude Desktop](https://cascades.crscreditapi.com/docs/mcp/install/claude-desktop.md) - [Cursor](https://cascades.crscreditapi.com/docs/mcp/install/cursor.md) - [VS Code](https://cascades.crscreditapi.com/docs/mcp/install/vscode.md) - [ChatGPT](https://cascades.crscreditapi.com/docs/mcp/install/chatgpt.md) --- --- title: Cascades in ChatGPT description: "Cascades in ChatGPT: config, install command and the scopes your API key needs for the Cascades MCP server." canonical_url: "https://cascades.crscreditapi.com/docs/mcp/install/chatgpt" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # Cascades in ChatGPT 1. ChatGPT connectors only speak **remote** MCP (a hosted Streamable-HTTP endpoint with OAuth). The Cascades server ships as a local stdio binary today, so there is nothing to paste yet. 2. A hosted endpoint with per-user authorization is on the roadmap; this page will carry the connector URL when it exists. 3. In the meantime, use Claude Code, Claude Desktop, Cursor, VS Code or Windsurf — all of which run the local server. ## Notes - Do not expose the server's HTTP mode (`MCP_TRANSPORT=http`) to the internet to work around this: it serves every caller with one process-level key and has no per-request auth. ## Other clients - [Claude Code](https://cascades.crscreditapi.com/docs/mcp/install/claude-code.md) - [Claude Desktop](https://cascades.crscreditapi.com/docs/mcp/install/claude-desktop.md) - [Cursor](https://cascades.crscreditapi.com/docs/mcp/install/cursor.md) - [VS Code](https://cascades.crscreditapi.com/docs/mcp/install/vscode.md) - [Windsurf](https://cascades.crscreditapi.com/docs/mcp/install/windsurf.md) --- --- title: cascades_create_workflow description: Create a new cascade (decision workflow) from a JSON definition, owned by the API key's account. canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_create_workflow" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_create_workflow` Create a new cascade (decision workflow) from a JSON definition, owned by the API key's account. Use this to turn a described policy into a runnable cascade: compose the node graph in `definition` and POST it. The cascade is active by default, so you can immediately run it with cascades_trigger_workflow using the returned id — pass active:false to create a draft instead. Sandbox accounts always receive a draft, which still runs in test mode. - **Access:** write - **Scopes:** `workflows:write` - **REST operation:** `POST /api/v1/workflows` — [createWorkflow](https://cascades.crscreditapi.com/docs/api/workflows/create-workflow.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `name` | `string` | yes | | | `definition` | `any` | 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. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_create_workflow", "arguments": { "name": "Credit-only prescreen", "active": false, "definition": [ { "id": "credit", "type": "BillingProduct", "billing_product_code": "TUC1001" } ] } } ``` ## Example result ```json { "data": { "id": 158, "object": "workflow", "name": "Credit-only prescreen", "active": false } } ``` --- --- title: cascades_delete_workflow description: Delete a cascade (soft-delete). canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_delete_workflow" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_delete_workflow` Delete a cascade (soft-delete). Afterwards it no longer appears in cascades_list_workflows and cannot be triggered. Requires the workflows:write scope; only cascades owned by the API key's account can be deleted. Confirm with the user before deleting. - **Access:** destructive - **Scopes:** `workflows:write` - **REST operation:** `DELETE /api/v1/workflows/{id}` — [deleteWorkflow](https://cascades.crscreditapi.com/docs/api/workflows/delete-workflow.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `workflow_id` | `integer` | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns `404`. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_delete_workflow", "arguments": { "workflow_id": 158 } } ``` ## Example result ```json { "deleted": true, "id": 158 } ``` --- --- title: cascades_generate_workflow description: Build a cascade from a plain-English policy description using the Cascades AI builder, then create it (by default) so it is immediately runnable with… canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_generate_workflow" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_generate_workflow` Build a cascade from a plain-English policy description using the Cascades AI builder, then create it (by default) so it is immediately runnable with cascades_trigger_workflow. Use this when the user describes a screening/decision policy in words rather than supplying a node graph. Returns the created cascade (with the AI-generated name and any warnings); pass create:false to preview the generated definition without saving it. - **Access:** write - **Scopes:** `workflows:write` - **REST operation:** `POST /api/v1/workflows/generate` — [generateWorkflow](https://cascades.crscreditapi.com/docs/api/workflows/generate-workflow.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `prompt` | `string` | yes | Plain-English description of the decision policy to turn into a cascade (max 5 000 characters by default). | | `name` | `string` | no | Optional name override; otherwise the generator names the cascade. | | `use_case` | `string` | no | Optional use case label. Defaults to `tenant_screening`. | | `create` | `boolean` | no | `true` (default) saves the generated cascade and returns `201`; `false` returns the generated definition as a `200` preview without saving. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_generate_workflow", "arguments": { "prompt": "Approve applicants with a credit score over 680 and no evictions; refer everyone else to review.", "use_case": "tenant_screening" } } ``` ## Example result ```json { "data": { "id": 159, "object": "workflow", "name": "Score 680+ and no evictions", "active": true }, "warnings": [] } ``` --- --- title: cascades_get_execution description: Fetch a single cascade execution by id to check its status and results. canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_get_execution" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_get_execution` Fetch a single cascade execution by id to check its status and results. Use this to poll after cascades_trigger_workflow until the run reaches a terminal status, and to read the decision: with full read access the response includes `step_outputs` (the per-step results) and `input_context`. Statuses: queued / pending / running / waiting / awaiting_review / completed / failed / cancelled / skipped. - **Access:** read - **Scopes:** `executions:read` - **REST operation:** `GET /api/v1/executions/{id}` — [getExecution](https://cascades.crscreditapi.com/docs/api/executions/get-execution.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `execution_id` | `integer` | yes | Execution id. Scoped to the calling api_client; a foreign id returns `404`. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_get_execution", "arguments": { "execution_id": 4821 } } ``` ## Example result ```json { "data": { "id": 4821, "object": "execution", "workflow_id": 157, "status": "completed", "decision": "approve", "step_outputs": { "credit": { "score": 712 }, "decision": { "outcome": "approve" } } } } ``` --- --- title: cascades_get_workflow description: Fetch one cascade by id, including its full node graph (definition). canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_get_workflow" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_get_workflow` Fetch one cascade by id, including its full node graph (`definition`). Use this to read a cascade before updating it with cascades_update_workflow, or to inspect how it is built. Only cascades owned by the API key's account are returned (a foreign or deleted id 404s). - **Access:** read - **Scopes:** `workflows:read` - **REST operation:** `GET /api/v1/workflows/{id}` — [getWorkflow](https://cascades.crscreditapi.com/docs/api/workflows/get-workflow.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `workflow_id` | `integer` | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns `404`. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_get_workflow", "arguments": { "workflow_id": 157 } } ``` ## Example result ```json { "data": { "id": 157, "object": "workflow", "name": "Tenant screening — standard", "active": true, "definition": [ { "id": "credit", "type": "BillingProduct", "billing_product_code": "TUC1001" } ] } } ``` --- --- title: cascades_list_test_cases description: List the pre-registered sandbox test-case subjects for a cascade — the synthetic applicants the CRS Testcase Service has authored for that cascade's… canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_list_test_cases" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_list_test_cases` List the pre-registered sandbox test-case subjects for a cascade — the synthetic applicants the CRS Testcase Service has authored for that cascade's products (e.g. specific credit-score bands). Use this when asked to "run the test cases", "try a sample applicant", or to sanity-check a cascade without hand-writing applicant data. Each result carries a ready-to-run `payload` (applicant + property) you pass straight to cascades_trigger_workflow with execution_mode "test". Returns an empty list when the cascade's products have no registered subjects. - **Access:** read - **Scopes:** `workflows:read` - **REST operation:** `GET /api/v1/workflows/{id}/test_cases` — [listWorkflowTestCases](https://cascades.crscreditapi.com/docs/api/workflows/list-workflow-test-cases.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `workflow_id` | `integer` | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns `404`. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_list_test_cases", "arguments": { "workflow_id": 157 } } ``` ## Example result ```json { "object": "test_case_list", "workflow_id": 157, "test_cases": [ { "id": 54, "case_name": "Cascades_Credit_720", "product_code": "TUC1001", "payload": { "applicant": { "first_name": "Sandrx", "last_name": "Bowkey" }, "property": { "state": "MD" } } } ] } ``` --- --- title: cascades_list_workflows description: List the caller's saved Cascades (decision workflows) — their id, code, name, use_case, trigger_type, and whether each is active. canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_list_workflows" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_list_workflows` List the caller's saved Cascades (decision workflows) — their id, code, name, use_case, trigger_type, and whether each is active. Use this first when the user refers to a cascade by name or asks "what can I run", to discover the workflow id you then pass to cascades_trigger_workflow. Only workflows owned by the API key's account are returned. Optional filters narrow the list. - **Access:** read - **Scopes:** `workflows:read` - **REST operation:** `GET /api/v1/workflows` — [listWorkflows](https://cascades.crscreditapi.com/docs/api/workflows/list-workflows.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `page` | `integer` | no | 1-based page number. | | `per_page` | `integer` | no | Items per page. Values above 100 are clamped to 100. | | `active` | `boolean` | no | Only active (`true`) or draft (`false`) workflows. | | `use_case` | `string` | no | Exact match, e.g. `tenant_screening`. | | `trigger_type` | `string` | no | Exact match. Not validated: a value outside the list returns an empty list, not an error. One of: `manual`, `event`, `scheduled`. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_list_workflows", "arguments": { "active": true } } ``` ## Example result ```json { "data": [ { "id": 157, "object": "workflow", "code": "WF-157", "name": "Tenant screening — standard", "use_case": "tenant_screening", "trigger_type": "manual", "active": true } ], "meta": { "page": 1, "per_page": 25, "total_count": 1, "total_pages": 1, "object": "list" } } ``` --- --- title: cascades_trigger_workflow description: Run a saved cascade for an applicant — i.e. start a decision. canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_trigger_workflow" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_trigger_workflow` Run a saved cascade for an applicant — i.e. start a decision. POSTs to /api/v1/workflows/:id/trigger and returns the created execution, which starts in status "pending" (the run is async). After calling this, poll cascades_get_execution with the returned execution id until status is terminal (completed / failed / cancelled / awaiting_review). execution_mode defaults to "test", which also works on draft cascades; "live" requires a production-approved account and an active cascade and is otherwise rejected. - **Access:** write - **Scopes:** `executions:write` - **REST operation:** `POST /api/v1/workflows/{id}/trigger` — [triggerWorkflow](https://cascades.crscreditapi.com/docs/api/workflows/trigger-workflow.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `workflow_id` | `integer` | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns `404`. | | `payload` | `object` | no | The decision input. Put the subject under `applicant`; arbitrary nested fields are allowed and become the execution's `input_context`. Defaults to `{}` when omitted. | | `execution_mode` | `string` | no | `test` uses synthetic vendor data and never bills; `live` requires production access and an active cascade. One of: `test`, `live`. | | `external_reference` | `string` | no | Your correlation id for this run. Echoed on the execution and on every webhook event. | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_trigger_workflow", "arguments": { "workflow_id": 157, "execution_mode": "test", "payload": { "applicant": { "first_name": "Sandrx", "last_name": "Bowkey" } } } } ``` ## Example result ```json { "data": { "id": 4821, "object": "execution", "workflow_id": 157, "status": "pending", "execution_mode": "test", "created_at": "2026-09-09T17:04:11Z" } } ``` --- --- title: cascades_update_workflow description: Update an existing cascade. canonical_url: "https://cascades.crscreditapi.com/docs/mcp/tools/cascades_update_workflow" doc_version: "1.0.0" last_updated: "2026-10-09T14:37:54Z" source: docs/mcp/tools.json --- # `cascades_update_workflow` Update an existing cascade. PARTIAL update — only the fields you pass change; omitted fields keep their current value. To edit the node graph, first read it with cascades_get_workflow, modify the `definition`, then pass the full new array back. Requires the workflows:write scope; you can only update cascades owned by the API key's account. - **Access:** write - **Scopes:** `workflows:write` - **REST operation:** `PATCH /api/v1/workflows/{id}` — [updateWorkflow](https://cascades.crscreditapi.com/docs/api/workflows/update-workflow.md) ## Input schema | Argument | Type | Required | Description | |---|---|---|---| | `workflow_id` | `integer` | yes | Workflow id. Only workflows owned by the token's account resolve; a foreign or deleted id returns `404`. | | `name` | `string` | no | | | `definition` | `any` | no | Replacement node graph (the full array, not a diff). Read the current one with `getWorkflow` first. | | `use_case` | `string` | no | | | `trigger_type` | `string` | no | Unknown values are ignored (the current value is kept). One of: `manual`, `event`, `scheduled`. | | `active` | `boolean` | no | `true` activates, `false` makes it a draft. Activating requires production access (`403 activation_forbidden` otherwise). | | `environment` | `string` | no | Target environment: "prod" (default) or "dev". One of: `dev`, `prod`. | ## Example call ```json { "name": "cascades_update_workflow", "arguments": { "workflow_id": 158, "active": true } } ``` ## Example result ```json { "data": { "id": 158, "object": "workflow", "name": "Credit-only prescreen", "active": true } } ``` --- --- title: Changelog description: What changed in the Cascades API, webhooks, MCP server and docs, newest first. canonical_url: "https://cascades.crscreditapi.com/docs/changelog" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" --- # Changelog ## 2026-09-09 — [Developer docs, OpenAPI operation ids and machine-readable twins](https://cascades.crscreditapi.com/docs/changelog/2026-09-09-docs-platform.md) New /docs site generated from the OpenAPI spec, operationIds on every operation, x-scopes and x-webhooks in the spec, markdown twins and llms.txt. ## 2026-09-04 — [List a cascade's registered test-case subjects](https://cascades.crscreditapi.com/docs/changelog/2026-09-04-test-cases-endpoint.md) GET /workflows/{id}/test_cases returns the sandbox subjects registered for a cascade's products, each with a payload ready for trigger. --- --- title: Developer docs, OpenAPI operation ids and machine-readable twins description: New /docs site generated from the OpenAPI spec, operationIds on every operation, x-scopes and x-webhooks in the spec, markdown twins and llms.txt. canonical_url: "https://cascades.crscreditapi.com/docs/changelog/2026-09-09-docs-platform" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/changelog/2026-09-09-docs-platform.md --- # Developer docs, OpenAPI operation ids and machine-readable twins ## What changed - **A docs site at `/docs`.** Concepts, guides, compliance notes and a reference page per endpoint, all rendered from `docs/**` and the OpenAPI spec. Signed-in readers see their own sandbox key in every sample and can run requests from the page. - **Every operation has an `operationId`** (`triggerWorkflow`, `getExecution`, …). The id is the slug of the operation's reference page and the name the MCP manifest and code samples use. - **Scopes, idempotency and polling are declared in the spec** as `x-scopes`, `x-idempotent` and `x-async`, and rendered as badges. The nine operations the MCP server wraps carry `x-mcp` with the tool definition. - **Webhook events are in the spec** under a root `x-webhooks` key shaped like OpenAPI 3.1 `webhooks`, one entry per event with its payload schema and an example. They render under [/docs/api/webhooks](/docs/api/webhooks). - **The spec documents what the API does today**: the previously missing `POST /workflows`, `PATCH`/`DELETE /workflows/{id}`, `POST /workflows/generate` and `GET /workflows/{id}/test_cases`; `422 missing_idempotency_key`; `429` with the JSON envelope and `Retry-After`; `adverse_action.retry_scheduled`; the split between `Execution` and `ExecutionFull`. - **Machine-readable twins.** Append `.md` to any docs URL or send `Accept: text/markdown` for clean markdown; `/openapi.json` and `/openapi.yaml` serve the spec; `/llms.txt` indexes the site for agents. - **Error responses carry `doc_url`** pointing at `/docs/api/errors/`. ## What you may need to do Nothing changes in request or response bodies. If you generate a client from the spec, regenerate it to pick up the operation ids — method names will change from generated defaults to the documented ids. --- --- title: List a cascade's registered test-case subjects description: GET /workflows/{id}/test_cases returns the sandbox subjects registered for a cascade's products, each with a payload ready for trigger. canonical_url: "https://cascades.crscreditapi.com/docs/changelog/2026-09-04-test-cases-endpoint" doc_version: "1.0.0" last_updated: "2026-09-23T19:55:02Z" source: docs/changelog/2026-09-04-test-cases-endpoint.md --- # List a cascade's registered test-case subjects ## What changed New endpoint [`GET /workflows/{id}/test_cases`](/docs/api/workflows/list-workflow-test-cases) (`workflows:read`). It walks the cascade's product nodes, maps each product to its family in the CRS test-case registry, and returns every registered subject with a `payload` shaped for `POST /workflows/{id}/trigger` — the same subjects the visual builder's sandbox tests panel runs. In test mode the subjects return schema mocks like any applicant (set `payload.scenario` to `fail_path` for decline branches); registry replay applies to live-mode sandbox runs. ```json { "data": { "object": "test_case_list", "workflow_id": 27, "test_cases": [ { "id": 54, "case_name": "Cascades_Credit_660_Inq0", "product_code": "TUC1001", "payload": { "applicant": { "…": "…" }, "property": { "…": "…" } } } ] } } ``` The endpoint is best-effort: an unreachable registry returns an empty list, never a 5xx. The MCP server exposes it as `cascades_list_test_cases`. Also in this release: - A **test-mode trigger on a draft cascade now runs** instead of being refused; live triggers on drafts still return `409 workflow_inactive` (2026-09-03). - **Cascade activation over the API is gated on production access**: sandbox accounts create drafts and get a `note`; `PATCH` with `active: true` returns `403 activation_forbidden` (2026-09-03). See [Test identities](/docs/sandbox/test-identities) and [Sandbox vs live](/docs/concepts/sandbox-vs-live).