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

# Workflows API examples

> Copy/paste code examples for listing workflows, retrieving workflow graphs, and fetching detailed workflow node configurations via the Loops API, SDKs, and CLI.

<Warning>
  Workflow API endpoints are currently in alpha and are subject to change.
</Warning>

## List workflows

Retrieve a paginated list of workflows, most recently updated first.

[API reference](/api-reference/list-workflows)\
[CLI reference](/cli/workflows#list)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops workflows list -o json
  ```

  ```js JavaScript theme={"dark"}
  const response = await fetch("https://app.loops.so/api/v1/workflows?perPage=20", {
    method: "GET",
    headers: {
      "Authorization": "Bearer <your-api-key>",
    },
  });

  const { data, pagination } = await response.json();
  const workflowId = data[0]?.id;
  const nextCursor = pagination.nextCursor;
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const { data, pagination } = await loops.listWorkflows({ perPage: 20 });
  const workflowId = data[0]?.id;
  const nextCursor = pagination.nextCursor;
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $result = $loops->workflows->list(per_page: 20);
  $workflow_id = $result['data'][0]['id'] ?? null;
  $next_cursor = $result['pagination']['nextCursor'];
  ```

  ```ruby Ruby SDK theme={"dark"}
  response = LoopsSdk::Workflows.list(perPage: 20)

  workflow_id = response["data"][0]&.dig("id")
  next_cursor = response["pagination"]["nextCursor"]
  ```

  ```python Python theme={"dark"}
  import requests

  response = requests.get(
      "https://app.loops.so/api/v1/workflows",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
      params={"perPage": 20},
  )

  body = response.json()
  workflows = body["data"]
  workflow_id = workflows[0]["id"] if workflows else None
  next_cursor = body["pagination"]["nextCursor"]
  ```
</CodeGroup>

## Get a workflow

Fetch a simplified workflow graph with node types, connections, and selected
display fields.

<Tip>
  Save `rootNodeId` and the keys in `nodes`. Use them to request full node
  details with [Get workflow node](/api-reference/get-workflow-node).
</Tip>

[API reference](/api-reference/get-workflow)\
[CLI reference](/cli/workflows#get)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops workflows get $workflowId -o json
  ```

  ```js JavaScript theme={"dark"}
  const response = await fetch(
    `https://app.loops.so/api/v1/workflows/${workflowId}`,
    {
      method: "GET",
      headers: {
        "Authorization": "Bearer <your-api-key>",
      },
    },
  );

  const workflow = await response.json();
  const rootNodeId = workflow.rootNodeId;
  const nodes = workflow.nodes;
  const firstNode = nodes[rootNodeId];
  const nextNodeIds = firstNode?.nextNodeIds ?? [];
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const workflow = await loops.getWorkflow(workflowId);
  const rootNodeId = workflow.rootNodeId;
  const nodes = workflow.nodes;
  const firstNode = nodes[rootNodeId];
  const nextNodeIds = firstNode?.nextNodeIds ?? [];
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $workflow = $loops->workflows->get(workflow_id: $workflow_id);
  $root_node_id = $workflow['rootNodeId'];
  $nodes = $workflow['nodes'];
  $first_node = $nodes[$root_node_id];
  $next_node_ids = $first_node['nextNodeIds'] ?? [];
  ```

  ```ruby Ruby SDK theme={"dark"}
  workflow = LoopsSdk::Workflows.get(workflow_id: workflow_id)

  root_node_id = workflow["rootNodeId"]
  nodes = workflow["nodes"]
  first_node = nodes[root_node_id]
  next_node_ids = first_node&.dig("nextNodeIds") || []
  ```

  ```python Python theme={"dark"}
  response = requests.get(
      f"https://app.loops.so/api/v1/workflows/{workflow_id}",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
  )

  workflow = response.json()
  root_node_id = workflow["rootNodeId"]
  nodes = workflow["nodes"]
  first_node = nodes.get(root_node_id)
  next_node_ids = first_node.get("nextNodeIds", []) if first_node else []
  ```
</CodeGroup>

## Get workflow node details

Use the simplified workflow graph to pick a `nodeId`, then fetch the full node
configuration.

[API reference](/api-reference/get-workflow-node)\
[CLI reference](/cli/workflows#nodes-get)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops workflows nodes get $workflowId $nodeId -o json
  ```

  ```js JavaScript theme={"dark"}
  const nodeId = rootNodeId;

  const response = await fetch(
    `https://app.loops.so/api/v1/workflows/${workflowId}/nodes/${nodeId}`,
    {
      method: "GET",
      headers: {
        "Authorization": "Bearer <your-api-key>",
      },
    },
  );

  const node = await response.json();

  if (node.typeName === "EventTrigger") {
    console.log(node.eventName, node.reEligible);
  }

  if (node.typeName === "SendEmailAction") {
    // Use emailMessageId to read or edit the email's content (see below).
    console.log(node.emailMessageId, node.subject);
  }
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const nodeId = rootNodeId;

  const node = await loops.getWorkflowNode(workflowId, nodeId);

  if (node.typeName === "EventTrigger") {
    console.log(node.eventName, node.reEligible);
  }

  if (node.typeName === "SendEmailAction") {
    console.log(node.subject);
  }
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $node_id = $root_node_id;

  $node = $loops->workflows->getNode(
    workflow_id: $workflow_id,
    node_id: $node_id,
  );

  if ($node['typeName'] === 'EventTrigger') {
    echo $node['eventName'], $node['reEligible'];
  }

  if ($node['typeName'] === 'SendEmailAction') {
    echo $node['emailMessageId'], $node['subject'];
  }
  ```

  ```ruby Ruby SDK theme={"dark"}
  node_id = root_node_id

  node = LoopsSdk::Workflows.get_node(
    workflow_id: workflow_id,
    node_id: node_id,
  )

  if node["typeName"] == "EventTrigger"
    puts node["eventName"], node["reEligible"]
  end

  if node["typeName"] == "SendEmailAction"
    puts node["subject"]
  end
  ```

  ```python Python theme={"dark"}
  node_id = root_node_id

  response = requests.get(
      f"https://app.loops.so/api/v1/workflows/{workflow_id}/nodes/{node_id}",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
  )

  node = response.json()

  if node["typeName"] == "EventTrigger":
      print(node.get("eventName"), node.get("reEligible"))

  if node["typeName"] == "SendEmailAction":
      # Use emailMessageId to read or edit the email's content (see below).
      print(node.get("emailMessageId"), node.get("subject"))
  ```
</CodeGroup>

## Walk connected nodes

After fetching a node, follow `nextNodeIds` to load the next steps in the
workflow.

<CodeGroup>
  ```bash CLI theme={"dark"}
  for nodeId in $nextNodeIds; do
    loops workflows nodes get $workflowId $nodeId -o json
  done
  ```

  ```js JavaScript theme={"dark"}
  async function getWorkflowNodes(workflowId, nodeIds) {
    const nodes = await Promise.all(
      nodeIds.map(async (nodeId) => {
        const response = await fetch(
          `https://app.loops.so/api/v1/workflows/${workflowId}/nodes/${nodeId}`,
          {
            method: "GET",
            headers: {
              "Authorization": "Bearer <your-api-key>",
            },
          },
        );

        return response.json();
      }),
    );

    return nodes;
  }

  const connectedNodes = await getWorkflowNodes(workflowId, nextNodeIds);
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  async function getWorkflowNodes(workflowId, nodeIds) {
    return Promise.all(
      nodeIds.map((nodeId) => loops.getWorkflowNode(workflowId, nodeId)),
    );
  }

  const connectedNodes = await getWorkflowNodes(workflowId, nextNodeIds);
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  function get_workflow_nodes(LoopsClient $loops, string $workflow_id, array $node_ids): array {
    return array_map(
      fn (string $node_id) => $loops->workflows->getNode(
        workflow_id: $workflow_id,
        node_id: $node_id,
      ),
      $node_ids,
    );
  }

  $connected_nodes = get_workflow_nodes($loops, $workflow_id, $next_node_ids);
  ```

  ```ruby Ruby SDK theme={"dark"}
  def get_workflow_nodes(workflow_id, node_ids)
    node_ids.map do |node_id|
      LoopsSdk::Workflows.get_node(
        workflow_id: workflow_id,
        node_id: node_id,
      )
    end
  end

  connected_nodes = get_workflow_nodes(workflow_id, next_node_ids)
  ```

  ```python Python theme={"dark"}
  def get_workflow_nodes(workflow_id, node_ids):
      nodes = []

      for node_id in node_ids:
          response = requests.get(
              f"https://app.loops.so/api/v1/workflows/{workflow_id}/nodes/{node_id}",
              headers={
                  "Authorization": "Bearer <your-api-key>",
              },
          )
          nodes.append(response.json())

      return nodes

  connected_nodes = get_workflow_nodes(workflow_id, next_node_ids)
  ```
</CodeGroup>

## Edit a workflow email's content

Each `SendEmailAction` node includes an `emailMessageId`. Pass that ID to the
email message content APIs to read or edit the email's subject, LMX body,
sender, and more.

First fetch the current content to get its `contentRevisionId`, then send an
update.

<Tip>
  Save the returned `contentRevisionId` after each update and pass it as
  `expectedRevisionId` on the next update to avoid `409 Conflict` errors caused
  by stale revisions.
</Tip>

Get email message [API reference](/api-reference/get-email-message) [CLI reference](/cli/email-messages#get)\
Update email message [API reference](/api-reference/update-email-message) [CLI reference](/cli/email-messages#update)

<CodeGroup>
  ```bash CLI theme={"dark"}
  loops email-messages get $emailMessageId -o json

  loops email-messages update $emailMessageId \
    --expected-revision-id $contentRevisionId \
    --subject "Welcome to Loops 👋" \
    --lmx '<Paragraph><Text>Thanks for signing up!</Text></Paragraph>'
  ```

  ```js JavaScript theme={"dark"}
  // emailMessageId comes from a SendEmailAction node (see above).
  const emailMessageId = node.emailMessageId;

  // Fetch the current content to get the latest contentRevisionId.
  const current = await fetch(
    `https://app.loops.so/api/v1/email-messages/${emailMessageId}`,
    {
      method: "GET",
      headers: {
        "Authorization": "Bearer <your-api-key>",
      },
    },
  ).then((response) => response.json());

  const response = await fetch(
    `https://app.loops.so/api/v1/email-messages/${emailMessageId}`,
    {
      method: "POST",
      headers: {
        "Authorization": "Bearer <your-api-key>",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        expectedRevisionId: current.contentRevisionId,
        subject: "Welcome to Loops 👋",
        lmx: "<Paragraph>Thanks for signing up!</Paragraph>",
      }),
    },
  );

  const updated = await response.json();
  const nextContentRevisionId = updated.contentRevisionId;
  ```

  ```js JavaScript SDK theme={"dark"}
  import { LoopsClient } from "loops";

  const loops = new LoopsClient("<your-api-key>");

  const emailMessageId = node.emailMessageId;

  const current = await loops.getEmailMessage(emailMessageId);

  const updated = await loops.updateEmailMessage(emailMessageId, {
    expectedRevisionId: current.contentRevisionId,
    subject: "Welcome to Loops 👋",
    lmx: "<Paragraph><Text>Thanks for signing up!</Text></Paragraph>",
  });

  const nextContentRevisionId = updated.contentRevisionId;
  ```

  ```php PHP SDK theme={"dark"}
  use Loops\LoopsClient;

  $loops = new LoopsClient('<your-api-key>');

  $email_message_id = $node['emailMessageId'];

  $current = $loops->emailMessages->get(email_message_id: $email_message_id);

  $updated = $loops->emailMessages->update(
    email_message_id: $email_message_id,
    expected_revision_id: $current['contentRevisionId'],
    subject: 'Welcome to Loops 👋',
    lmx: '<Paragraph><Text>Thanks for signing up!</Text></Paragraph>',
  );

  $next_content_revision_id = $updated['contentRevisionId'];
  ```

  ```ruby Ruby SDK theme={"dark"}
  email_message_id = node["emailMessageId"]

  current = LoopsSdk::EmailMessages.get(email_message_id: email_message_id)

  updated = LoopsSdk::EmailMessages.update(
    email_message_id: email_message_id,
    expected_revision_id: current["contentRevisionId"],
    subject: "Welcome to Loops 👋",
    lmx: "<Paragraph><Text>Thanks for signing up!</Text></Paragraph>",
  )

  next_content_revision_id = updated["contentRevisionId"]
  ```

  ```python Python theme={"dark"}
  # email_message_id comes from a SendEmailAction node (see above).
  email_message_id = node["emailMessageId"]

  # Fetch the current content to get the latest contentRevisionId.
  current = requests.get(
      f"https://app.loops.so/api/v1/email-messages/{email_message_id}",
      headers={
          "Authorization": "Bearer <your-api-key>",
      },
  ).json()

  response = requests.post(
      f"https://app.loops.so/api/v1/email-messages/{email_message_id}",
      headers={
          "Authorization": "Bearer <your-api-key>",
          "Content-Type": "application/json",
      },
      json={
          "expectedRevisionId": current["contentRevisionId"],
          "subject": "Welcome to Loops 👋",
          "lmx": "<Paragraph>Thanks for signing up!</Paragraph>",
      },
  )

  updated = response.json()
  next_content_revision_id = updated["contentRevisionId"]
  ```
</CodeGroup>
