Guides
Your first screening
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.
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.
-
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 apayloadshaped for the trigger endpoint:cURLcurl "https://cascades.crscreditapi.com/api/v1/workflows/27/test_cases" \ -H "Authorization: Bearer {{API_KEY}}"Node.jsconst 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);Pythonimport 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"])Rubyrequire "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")Gopackage 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)) }Agentclaude 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_replayistrue; otherwise every product node returns its schema mock. See 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
payloadas the trigger body with a uniqueIdempotency-Key(use your own application id) and anexternal_referenceyou can correlate on later:cURLcurl -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.jsconst 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);Pythonimport 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"])Rubyrequire "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")Gopackage 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)) }Agentclaude 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_pathmock by default and the applicant is approved. To walk the decline path in test mode, add"scenario": "fail_path"topayload— every product node then returns its decline mock (the eviction node, for example, reports two filings andrecommendation: 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. Keepdata.id— that is the execution — andmeta.request_idfor support.Try it
POST/workflows/{id}/triggerSign in to run this against your sandbox — the console uses a key that only ever runs in test mode.
Sign in to try it -
Poll until it settles
Poll every second or two until
statusiscompleted,failed,cancelled,skipped, orawaiting_review:cURLcurl "https://cascades.crscreditapi.com/api/v1/executions/1842" \ -H "Authorization: Bearer {{API_KEY}}"Node.jsconst 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);Pythonimport 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"])Rubyrequire "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")Gopackage 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)) }Agentclaude 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:fullthe response includesstep_outputs: one entry per node, keyed by node id, holding the report fields the decision used (for the credit subject,creditScore.fico9and the inquiry counts) and which branch eachIfElsetook.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:cURLcurl -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.jsconst 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);Pythonimport 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"])Rubyrequire "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")Gopackage 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)) }Agentclaude 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.approvedcontinues downpass_steps;deniedcontinues downfail_steps; an adverse-action node inhuman_review_stepsprepared its notice draft when the run was flagged. The whole decision trail is atGET /executions/{id}/review_events. More in 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 atGET /executions/{id}/notice_artifacts. Listing needsexecutions:read; downloading a file needsexecutions:read:full.cURLcurl "https://cascades.crscreditapi.com/api/v1/executions/1842/notice_artifacts" \ -H "Authorization: Bearer {{API_KEY}}"Node.jsconst 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);Pythonimport 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"])Rubyrequire "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")Gopackage 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)) }Agentclaude 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 —
execution.completedarrives seconds after the transition. - Run many applicants at once with a batch.
- When your account is approved, flip
execution_modetolive: Sandbox vs live.