> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cortex.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Example Workflow definitions

> Complete Workflow definitions you can create with the Workflows API or manage as YAML

This page has complete, working Workflow definitions that you can send to the [Workflows API](https://docs.cortex.io/api/rest/workflows) as they are, or save as YAML files with [Workflows as code](https://docs.cortex.io/streamline/workflows/workflows-as-code.md). Each example shows how blocks pass data to each other, so you can use them as a starting point for your own Workflows.

| Example | What it shows |
| - | - |
| [Look up an entity's owners](#look-up-an-entitys-owners) | An HTTP request to the Cortex API, a Data transformation (jq) block, and an entity-scoped Workflow |
| [Branch on user input](#branch-on-user-input) | A User input block and a Branch block that takes a different path based on the user's answer |
| [Send a Slack notification](#send-a-slack-notification) | A Workflow variable set at run time, and a Slack block with a templated message |
| [Wait for an external system](#wait-for-an-external-system) | An Asynchronous HTTP request block that pauses until an external system calls back |

For the fields each block takes, see the [Workflow block configuration reference](https://docs.cortex.io/streamline/workflows/blocks/block-configuration-reference.md).

## Before you begin

* Create an [API key](https://docs.cortex.io/configure/settings/api-keys) with the `Edit Workflows` permission. To run Workflows with the API, the key also needs the `Execute Workflow Runs` permission.
* Find the [base URL](https://docs.cortex.io/api.md#base-url) for your workspace. The commands on this page use the US base URL, `https://api.getcortexapp.com`.
* Check the prerequisites listed with each example. Some examples need an integration to be configured in your workspace.

## Creating a Workflow from a definition

The Workflows API accepts a definition as YAML (`Content-Type: application/yaml`) or JSON (`Content-Type: application/json`). The keys are the same in both formats. If a Workflow with the same `tag` already exists, Cortex replaces it with the new definition.

**1. Save the definition**

Copy an example from this page and save it as a file, such as `workflow.yaml`.

**2. Validate the definition**

Send the file to the [Validates YAML descriptor](https://docs.cortex.io/api/rest/workflows/validates-yaml-descriptor.md) endpoint. Cortex checks the definition without saving it, and returns `ok` and a list of `violations`.

```bash theme={null}
curl -X POST https://api.getcortexapp.com/api/v1/workflows/validate \
  -H "Authorization: Bearer $CORTEX_API_KEY" \
  -H "Content-Type: application/yaml" \
  --data-binary @workflow.yaml
```

**3. Create the Workflow**

Send the same file to the [Create or update workflow](https://docs.cortex.io/api/rest/workflows/create-or-update-workflow.md) endpoint.

```bash theme={null}
curl -X POST https://api.getcortexapp.com/api/v1/workflows \
  -H "Authorization: Bearer $CORTEX_API_KEY" \
  -H "Content-Type: application/yaml" \
  --data-binary @workflow.yaml
```

**4. Run the Workflow**

Run it from the Cortex UI, or with the [Run a workflow](https://docs.cortex.io/api/rest/workflows/run-a-workflow.md) endpoint. Each example below includes a run request.

> [!NOTE]
>
> Use `--data-binary` rather than `-d` to send a YAML file. `-d` removes line breaks, which breaks the YAML.

## Look up an entity's owners

This Workflow runs on a service. It calls the Cortex API to get the entity's details, then uses a Data transformation block to pull out the names of the teams that own it. When the run finishes, Cortex shows the owners in the run response.

**Prerequisites:** An admin with the `Edit Settings` permission must turn on the Cortex integration for HTTP blocks, under **Settings** > **Workflows** > **HTTP block**. If you've limited the integration's allowed actions, **Read** must stay on. See [Controlling which integrations HTTP blocks can use](https://docs.cortex.io/streamline/workflows/blocks#controlling-which-integrations-http-blocks-can-use).

> [!NOTE]
>
> In newer workspaces, every integration starts off for HTTP blocks. If the Cortex integration is off, the run fails with the message "Cortex integration is disabled for HTTP blocks. Integrations can be enabled in settings."

```yaml theme={null}
name: Look up entity owners
tag: look-up-entity-owners
description: Lists the teams that own a service.
isDraft: false
isRunnableViaApi: true
restrictActionCompletionToRunnerUser: false
filter:
  type: ENTITY
  ownershipScope: ALL
  requiredTeamRoles: []
  entityFilter:
    typeFilter:
      types:
      - service
runResponseTemplate: "Owners of {{context.entity.name}}: {{actions.list-owners.outputs.result}}"
variables: []
actions:
- name: Get entity
  slug: get-entity
  isRootAction: true
  outgoingActions:
  - list-owners
  schema:
    type: HTTP_REQUEST
    integration: Cortex
    httpMethod: GET
    url: /api/v1/catalog/{{context.entity.tag}}
    headers: {}
- name: List owners
  slug: list-owners
  isRootAction: false
  outgoingActions: []
  schema:
    type: JQ
    expression: >-
      (.actions["get-entity"].outputs.body.ownership.groups // [])
      | map(.groupName)
      | if length == 0 then "no owners" else join(", ") end
```

**How it works:**

* `filter.type: ENTITY` makes this an entity-scoped Workflow, and `entityFilter` limits it to services. Because it runs on an entity, the blocks can read `context.entity`.
* The HTTP request block uses `integration: Cortex`, so Cortex adds the base URL and credentials. The `url` is a relative path that templates in the entity's tag.
* The HTTP request block writes the parsed response to `actions.get-entity.outputs.body`. The jq expression reads it with a leading dot, and quotes the slug because it contains a hyphen.
* `runResponseTemplate` shows the jq result to the user when the run finishes.

**Run it with the API:**

```bash theme={null}
curl -X POST https://api.getcortexapp.com/api/v1/workflows/look-up-entity-owners/runs \
  -H "Authorization: Bearer $CORTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "ENTITY", "entityId": "my-service"}}'
```

Replace `my-service` with the tag of a service in your workspace.

## Branch on user input

This Workflow asks the user for a service name and a target environment, then takes a different path depending on the environment. Production deployments get a reminder to open a change request, and every other environment takes the fallback path.

**Prerequisites:** None.

```yaml theme={null}
name: Plan a deployment
tag: plan-a-deployment
description: Builds a deployment checklist based on the target environment.
isDraft: false
isRunnableViaApi: false
restrictActionCompletionToRunnerUser: true
filter:
  type: GLOBAL
runResponseTemplate: null
variables: []
actions:
- name: Choose environment
  slug: choose-environment
  isRootAction: true
  outgoingActions:
  - check-environment
  schema:
    type: USER_INPUT
    inputs:
    - type: INPUT_FIELD
      name: Service name
      key: service-name
      required: true
      validationRegex: ^[a-z][a-z0-9-]+$
    - type: SELECT_FIELD
      name: Environment
      key: environment
      required: true
      options: [dev, staging, prod]
      optionsLabels: [Development, Staging, Production]
      allowAdditionalOptions: false
    inputOverrides: []
    jsValidatorScript: ""
- name: Check environment
  slug: check-environment
  isRootAction: false
  outgoingActions:
  - production-plan
  - standard-plan
  schema:
    type: CONDITIONAL_BRANCH
    branches:
    - type: CONDITIONAL
      name: Production
      slug: production
      expression: actions["choose-environment"].outputs.environment == "prod"
      outgoingAction: production-plan
    fallbackBranch:
      type: FALLBACK
      name: Other environments
      slug: other-environments
      outgoingAction: standard-plan
- name: Production plan
  slug: production-plan
  isRootAction: false
  outgoingActions: []
  schema:
    type: JQ
    expression: >-
      "Deploy \(.actions["choose-environment"].outputs["service-name"]) to production.
      Open a change request and get approval before you merge."
- name: Standard plan
  slug: standard-plan
  isRootAction: false
  outgoingActions: []
  schema:
    type: JQ
    expression: >-
      "Deploy \(.actions["choose-environment"].outputs["service-name"])
      to \(.actions["choose-environment"].outputs.environment). No approval needed."
```

**How it works:**

* The User input block pauses the run until the user submits the form. Each field's `key` becomes an output, such as `actions.choose-environment.outputs.environment`.
* The Branch block's `outgoingActions` lists the first block of every path, including the fallback path.
* The `production` path's `expression` is a CEL expression. It reads the user's answer without a leading dot.
* The paths don't join again, so the Branch block doesn't set `joiningAction`. Each path ends with a block whose `outgoingActions` is `[]`.
* `restrictActionCompletionToRunnerUser: true` means only the person who started the run can fill in the form.

The user fills in the form in Cortex, so this Workflow sets `isRunnableViaApi: false` and is meant to run from the UI.

## Send a Slack notification

This Workflow posts a release announcement to a Slack channel. The release version comes from a Workflow variable, so you can set it when you start the run, which makes this Workflow a good fit for a CI/CD pipeline.

**Prerequisites:** Configure the [Slack integration](https://docs.cortex.io/ingesting-data-into-cortex/integrations/slack), and make sure Cortex can post to the channel (see the Slack block in [Adding blocks to a Workflow](https://docs.cortex.io/streamline/workflows/blocks)). Replace `release-announcements` with the name of your channel, without the `#`.

```yaml theme={null}
name: Announce a release
tag: announce-a-release
description: Posts a release announcement to Slack.
isDraft: false
isRunnableViaApi: true
restrictActionCompletionToRunnerUser: false
filter:
  type: GLOBAL
runResponseTemplate: "Announced release {{variables.release-version}}."
variables:
- slug: release-version
  type: STRING
  description: The version being released.
  defaultValue: "0.0.0"
actions:
- name: Notify channel
  slug: notify-channel
  isRootAction: true
  outgoingActions: []
  schema:
    type: SLACK
    channel: release-announcements
    message: "Release {{variables.release-version}} is rolling out. Started by {{context.initiatedBy.name}}."
```

**How it works:**

* `variables` declares `release-version` with a default value. Blocks read it as `{{variables.release-version}}`.
* When you run the Workflow with the API, set the variable in `initialContext`, keyed by its slug. If you leave it out, Cortex uses the default.
* The Slack block's `message` is a template, so it can include values from the run state, such as the name of the user who started the run.

**Run it with the API:**

```bash theme={null}
curl -X POST https://api.getcortexapp.com/api/v1/workflows/announce-a-release/runs \
  -H "Authorization: Bearer $CORTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "GLOBAL"}, "initialContext": {"release-version": "2.4.0"}}'
```

> [!NOTE]
>
> Slack blocks can't read secrets from `context.secrets`.

## Wait for an external system

This Workflow asks the user for an environment name, sends a request to an external provisioning service, and pauses until that service reports back. It's useful for work that takes longer than a single HTTP request, such as provisioning infrastructure or running a CI pipeline.

**Prerequisites:** An external service that accepts the request and can call the Cortex API when it's done. Replace `https://provisioner.example.com/environments` with your service's URL.

```yaml theme={null}
name: Provision an environment
tag: provision-an-environment
description: Asks an external service to provision an environment and waits for it to finish.
isDraft: false
isRunnableViaApi: false
restrictActionCompletionToRunnerUser: true
filter:
  type: GLOBAL
runResponseTemplate: "Environment ready: {{actions.read-environment-id.outputs.result}}"
variables: []
actions:
- name: Request environment
  slug: request-environment
  isRootAction: true
  outgoingActions:
  - provision
  schema:
    type: USER_INPUT
    inputs:
    - type: INPUT_FIELD
      name: Environment name
      key: env-name
      required: true
      validationRegex: ^[a-z][a-z0-9-]+$
    inputOverrides: []
    jsValidatorScript: ""
- name: Provision
  slug: provision
  isRootAction: false
  outgoingActions:
  - read-environment-id
  schema:
    type: HTTP_REQUEST_ASYNC
    httpMethod: POST
    url: https://provisioner.example.com/environments
    headers:
      Content-Type: application/json
    payload: '{"name": "{{actions.request-environment.outputs.env-name}}", "callbackUrl": "{{callbackUrl}}"}'
    timeoutInSeconds: 3600
- name: Read environment ID
  slug: read-environment-id
  isRootAction: false
  outgoingActions: []
  schema:
    type: JQ
    expression: .actions.provision.outputs.result.output.envId
```

**How it works:**

* When the Asynchronous HTTP request block runs, Cortex replaces `{{callbackUrl}}` with a URL that's unique to this run and block, sends the request, and pauses the Workflow. Always use two curly braces for `{{callbackUrl}}`.
* The external service calls the callback URL when it's done. If it doesn't call back within `timeoutInSeconds` (one hour here), the block fails with the status `timed_out`.
* The callback's `response` object becomes `actions.provision.outputs.result.output`, so the next block can read the values the external service sent back.

**How the external service calls back:**

The service sends a `POST` request to the callback URL with a Cortex API key that has the `Execute Workflow Runs` permission:

```bash theme={null}
curl -X POST "$CALLBACK_URL" \
  -H "Authorization: Bearer $CORTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "SUCCESS", "message": "Environment created.", "response": {"envId": "env-1234"}}'
```

Set `status` to `SUCCESS` to continue the Workflow, `FAILURE` to fail the block, or `UPDATE` to report progress and keep waiting. For the full request format, see [Update asynchronous HTTP request Workflow block](https://docs.cortex.io/api/rest/workflows/update-asynchronous-http-request-workflow-block.md).

## Adapting the examples

* **Change the `tag`** before you create a copy. Creating a Workflow with an existing tag replaces that Workflow.
* **Keep slugs and references in sync.** If you rename a block's `slug`, update every `outgoingActions`, `outgoingAction`, and template that refers to it.
* **Start in the UI when you're unsure.** Build the Workflow in the Workflow editor, then export it as YAML (see [Workflows as code](https://docs.cortex.io/streamline/workflows/workflows-as-code.md#step-2-creating-a-yaml-file-for-the-workflow)) to get the exact values for your workspace, such as team IDs and integration aliases.
* **Reference run data** with the syntax each field expects. See [Referencing run data in block fields](https://docs.cortex.io/streamline/workflows/blocks/block-configuration-reference.md#referencing-run-data-in-block-fields).


## Related topics

- [Workflows](https://docs.cortex.io/api/rest/workflows.md)
- [Workflow block configuration reference](https://docs.cortex.io/streamline/workflows/blocks/block-configuration-reference.md)
- [Workflows as code](https://docs.cortex.io/streamline/workflows/workflows-as-code.md)
