> This page is for Tools.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.moveworks.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.moveworks.com/_mcp/server.

# Compound Action Patterns

This document's  purpose is to accelerate your development process by providing a collection of reusable patterns for common tasks.

Instead of showcasing entire\*\* end-to-end\*\* use cases, this guide focuses on individual, common "steps" you'll encounter while building. For each pattern, we recommend the most efficient method, whether it's an LLM action, a Moveworks Data Mapper expression, a DSL query, or a Python Script.

Let's dive into the patterns.

# Performing Actions in Batch

**Problem**: You want to perform a series of actions repetitively for a list of elements. For example, sending a notification to a group of users.

#### List of users

```json
{
  "data": {
    "email_list": [
      "jane.doe@example.com",
      "john_smith88@gmail.com",
      "alice.w@yahoo.com",
      "support@mybusiness.net",
      "data-report-user@company.org",
      "tester_01@outlook.com"
    ]
  }
}
```

#### Usage Compound Action

```yaml
steps:
  - action:
      action_name: mw.batch_get_users_by_email
      output_key: list_of_users
      input_args:
        user_emails: data.email_list
  - for:
      output_key: list_of_notifications
      each: record
      in: data.list_of_users.user_records
      index: idx
      steps:
        - notify:
            output_key: notification_output
            recipient_id: record.lookup_id
            message:
              RENDER():
                args:
                  user_name: record.user.full_name
                template: Hi {{user_name}}, you have not submitted your project update this week. Please do before the end of the day!
        - action:
            action_name: do_some_action_for_user
            output_key: action_result
            input_args:
              user: record.user
...
```

# Deduplicating a List

**Problem**: You have a list that contains duplicate values, and you need a list with only unique elements.

### Deduplicating a list of scalars

#### Input

```json
{
  "data": {
    "duplicated_fruit_list": ["apple", "banana", "cherry", "apple", "banana", "date"]
  }
}
```

```python
seen = set()
unique = []
for x in elements:
    if x in seen:
        continue
    seen.add(x)
    unique.append(x)
return unique
```

#### Usage in Compound Action

```yaml
steps:
  - action:
      action_name: deduplicate_list
      output_key: deduplicate_list_result
      input_args:
        elements: data.duplicated_fruit_list
  - return:
      output_mapper:
        unique_list: data.deduplicate_list_result
```

#### Result

```json
{
  "unique_list": ["apple", "banana", "cherry", "date"]
}

```

### Deduplicating a list of objects

In this case we will deduplicating a list of objects that have the same `email` key

```python
seen = set()
out = []
for obj in elements:
    if not isinstance(obj, dict) or key_field not in obj:
        continue
    k = obj[key_field]
    if k in seen:
        continue
    seen.add(k)
    out.append(obj)
return out
```

#### Usage in Compound Action

```yaml
steps:
  - action:
      action_name: deduplicate_object_list
      output_key: deduplicate_list_result
      input_args:
        elements: data.input_list
        key_field: '''email'''
  - return:
      output_mapper:
        unique_list: data.deduplicate_list_result
```

---

# Sort Alphabetically

**Problem**: You need to provide a list of elements sorted alphabetically

#### Input Arguments

```json
{
  "data":{
  "fruits": ["red", "green", "blue", "yellow", "purple"]
  }
}
```

#### Compound Action

```yaml
steps:
  - action:
      action_name: enter_fruits_into_system
      output_key: fruits_result
      input_args:
        sorted_fruits:
          SORT():
            items: data.fruits
            key: item
```

#### Result

```json
{
  "sorted_fruits": [
    "blue",
    "green",
    "purple",
    "red",
    "yellow"
  ]
}
```

---

# Sort Timestamps

**Problem**: You want to sort a list based on the timestamp

### Sorting a list of timestamps

#### input

```json
{
  "data": {
    "ts": [
      "Monday, Sep 22, 2025 at 8:15 AM MDT",
      "Saturday, Sep 20, 2025 at 10:30 AM MDT",
      "Friday, Sep 19, 2025 at 5:07 PM MDT",
      "Friday, Sep 19, 2025 at 9:00 PM MDT"
    ]
  }
}

```

#### Compound Action

```yaml
steps:
  - action:
      action_name: enter_timestamps
      output_key: timestamps_result
      input_args:
        sorted_timestamps:
          SORT():
            items: data.ts
            key: item.$PARSE_TIME()
```

#### Result

```json
{
  "sorted_timestamps": [
    "Friday, Sep 19, 2025 at 5:07 PM MDT",
    "Friday, Sep 19, 2025 at 9:00 PM MDT",
    "Saturday, Sep 20, 2025 at 10:30 AM MDT",
    "Monday, Sep 22, 2025 at 8:15 AM MDT"
  ]
}
```

# Sorting a list of objects by timestamp

#### Input

```json
{
  "data": {
    "finances": [
      {
        "id": "txn_k5p1",
        "ts": "2025-09-20T10:30:00-06:00",
        "amount": 2500.0
      },
      {
        "id": "txn_z7h3",
        "ts": "2025-09-22T08:15:00-06:00",
        "amount": -29.99
      },
      {
        "id": "txn_8x4f",
        "ts": "2025-09-19T17:14:04-06:00",
        "amount": -8.5
      },
      {
        "id": "txn_a2d9",
        "ts": "2025-09-19T21:00:00-06:00",
        "amount": 150.75
      }
    ]
  }
}

```

#### Compound Action

```yaml
steps:
  - action:
      action_name: enter_finances
      output_key: finances_result
      input_args:
        sorted_financials:
          SORT():
            items: data.finances
            key: item.ts.$PARSE_TIME()

```

#### Result

```json
{
  "sorted_financials": [
    {
      "amount": -8.5,
      "id": "txn_8x4f",
      "ts": "2025-09-19T17:14:04-06:00"
    },
    {
      "amount": 150.75,
      "id": "txn_a2d9",
      "ts": "2025-09-19T21:00:00-06:00"
    },
    {
      "amount": 2500,
      "id": "txn_k5p1",
      "ts": "2025-09-20T10:30:00-06:00"
    },
    {
      "amount": -29.99,
      "id": "txn_z7h3",
      "ts": "2025-09-22T08:15:00-06:00"
    }
  ]
}
```

# Pagination with For Loops

**Problem**: You need to paginate through an API that returns results page-by-page using a pagination token (e.g., `nextPageToken`), but compound actions don't support `while` loops.

**Solution**: Use a `for` loop with a fixed range (0 to `MAX_PAGES`), and on each iteration:

1. Read the pagination token from the *previous* iteration's output using `index - 1`
2. Use a `condition` to skip iterations once there are no more pages
3. Pass the token (or `nil` for the first page) into the HTTP action

> **Warning**
>
> **Do not use parallel expressions** with this pattern. Because each iteration reads from the previous iteration's output in the data tree, the loop must execute sequentially.

> **Info**
>
> **Why not a while loop?** Open-ended while loops are not supported in compound actions for safety reasons — a misconfigured loop could run indefinitely. The for-loop-with-condition pattern gives you the same pagination behavior with a guaranteed upper bound on iterations.

#### Setup

First, create a helper action (e.g., a Script Action) that generates a range list for the for loop to iterate over:

```python
# Action: generate_page_range
# Input: max_pages (int)
# Output: list of integers [0, 1, 2, ..., max_pages - 1]
return list(range(max_pages))
```

#### Compound Action

```yaml
steps:
  # Step 1: Generate a range to iterate over (e.g., max 10 pages)
  - action:
      action_name: generate_page_range
      output_key: page_range
      input_args:
        max_pages: 10

  # Step 2: Paginate through the API
  - for:
      each: page
      index: page_index
      in: data.page_range
      output_key: paginated_results
      steps:
        - action:
            action_name: fetch_items_page
            output_key: fetch_result
            condition: page_index == 0 OR data.paginated_results[page_index - 1].fetch_result.nextPageToken != nil
            input_args:
              page_size: 100
              pagination_token: IF (page_index == 0) THEN nil ELSE data.paginated_results[page_index - 1].fetch_result.nextPageToken
```

#### How It Works

| Iteration       | `page_index` | `pagination_token`                                                            | `condition`                                                     |
| :-------------- | :----------- | :---------------------------------------------------------------------------- | :-------------------------------------------------------------- |
| First           | `0`          | `nil` (first page, no token needed)                                           | `true` (always runs)                                            |
| Subsequent      | `1, 2, ...`  | Read from `data.paginated_results[page_index - 1].fetch_result.nextPageToken` | Only runs if previous iteration returned a `nextPageToken`      |
| After last page | `N`          | N/A                                                                           | `false` — skips execution since no `nextPageToken` was returned |

#### Extracting All Results

After the loop completes, you can use a Script Action to flatten all pages into a single list:

```python
# Action: flatten_paginated_results
# Input: paginated_results (list of dicts from the for loop)
all_items = []
for page in paginated_results:
    result = page.get("fetch_result")
    if result and result.get("items"):
        all_items.extend(result["items"])
return all_items
```

#### Real-World Example: Polling an API Until Success

This same pattern works for polling use cases (e.g., waiting for an Okta push verification):

```yaml
steps:
  - action:
      action_name: generate_page_range
      output_key: poll_range
      input_args:
        max_pages: 12

  - action:
      action_name: send_okta_push_verification
      output_key: push_result
      input_args:
        user_id: data.user_id

  - for:
      each: attempt
      index: attempt_index
      in: data.poll_range
      output_key: poll_results
      steps:
        - action:
            action_name: check_okta_push_status
            output_key: status_check
            condition: >
              attempt_index == 0
              OR data.poll_results[attempt_index - 1].status_check.factorResult == 'WAITING'
            input_args:
              poll_url: data.push_result.poll_url
        - delay:
            seconds: 5
            condition: >
              data.poll_results[attempt_index].status_check.factorResult == 'WAITING'
```

---

# Easiest way to multiline string usage in YAML

**Problem**: You want a multiline string's structure to be maintained

**Solution**:  Use **Render()** and the | character with your template value indented

#### Compound Action

```yaml
result:
  display_instruction_for_model:
    RENDER():
      template: |
        Each row = one unique order line item in a customer’s purchase.
        Uniqueness: LINE_ITEM_ID is the primary key (unique per row).
        Aggregation grain:
          - Line item–level -> count rows / COUNT(DISTINCT LINE_ITEM_ID)
          - Order-level -> COUNT(DISTINCT ORDER_ID)
          - Customer-level -> COUNT(DISTINCT CUSTOMER_ID)
          - Product-level -> COUNT(DISTINCT PRODUCT_ID)
        Rule: Always aggregate at the appropriate grain to ensure metrics align correctly with the business question.
```

\
\