> ## 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.

<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.

[List workflows API reference](/api-reference/list-workflows)

<CodeGroup>
  ```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;
  ```

  ```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>

[Get workflow API reference](/api-reference/get-workflow)

<CodeGroup>
  ```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 ?? [];
  ```

  ```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.

[Get workflow node API reference](/api-reference/get-workflow-node)

<CodeGroup>
  ```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);
  }
  ```

  ```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>
  ```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);
  ```

  ```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 exposes an `emailMessageId`. Pass that ID to the
email message content APIs to read or edit the email's subject, LMX body,
sender, and more — the same endpoints used for campaign and transactional
emails.

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)\
[Update email message API reference](/api-reference/update-email-message)

<CodeGroup>
  ```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><Text>Thanks for signing up!</Text></Paragraph>",
      }),
    },
  );

  const updated = await response.json();
  const nextContentRevisionId = 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><Text>Thanks for signing up!</Text></Paragraph>",
      },
  )

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