Skip to main content
Workflow API endpoints are currently in alpha and are subject to change.

List workflows

Retrieve a paginated list of workflows, most recently updated first. List workflows API reference
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;

Get a workflow

Fetch a simplified workflow graph with node types, connections, and selected display fields.
Save rootNodeId and the keys in nodes. Use them to request full node details with Get workflow node.
Get workflow API reference
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 ?? [];

Get workflow node details

Use the simplified workflow graph to pick a nodeId, then fetch the full node configuration. Get workflow node API reference
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);
}

Walk connected nodes

After fetching a node, follow nextNodeIds to load the next steps in the workflow.
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);

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.
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.
Get email message API reference
Update email message API reference
// 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;
Last modified on June 24, 2026