loops_sdk 2.3.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -120,28 +120,54 @@ You can use custom contact properties in API calls. Please make sure to [add cus
120
120
  - [Transactional.update()](#transactionalupdate)
121
121
  - [Transactional.ensure_draft()](#transactionalensure_draft)
122
122
  - [Transactional.publish()](#transactionalpublish)
123
+ - [Transactional.metrics()](#transactionalmetrics)
123
124
  - [Transactional.send()](#transactionalsend)
124
125
  - [DedicatedSendingIps.list()](#dedicatedsendingipslist)
125
126
  - [Themes.list()](#themeslist)
126
127
  - [Themes.get()](#themesget)
128
+ - [Themes.create()](#themescreate)
129
+ - [Themes.update()](#themesupdate)
127
130
  - [Components.list()](#componentslist)
128
131
  - [Components.get()](#componentsget)
132
+ - [Components.create()](#componentscreate)
133
+ - [Components.update()](#componentsupdate)
129
134
  - [Campaigns.list()](#campaignslist)
130
135
  - [Campaigns.create()](#campaignscreate)
131
136
  - [Campaigns.get()](#campaignsget)
132
137
  - [Campaigns.update()](#campaignsupdate)
138
+ - [Campaigns.metrics()](#campaignsmetrics)
133
139
  - [CampaignGroups.list()](#campaigngroupslist)
134
140
  - [CampaignGroups.create()](#campaigngroupscreate)
135
141
  - [CampaignGroups.get()](#campaigngroupsget)
136
142
  - [CampaignGroups.update()](#campaigngroupsupdate)
143
+ - [WorkflowGroups.list()](#workflowgroupslist)
144
+ - [WorkflowGroups.create()](#workflowgroupscreate)
145
+ - [WorkflowGroups.get()](#workflowgroupsget)
146
+ - [WorkflowGroups.update()](#workflowgroupsupdate)
137
147
  - [AudienceSegments.list()](#audiencesegmentslist)
138
148
  - [AudienceSegments.get()](#audiencesegmentsget)
149
+ - [AudienceSegments.create()](#audiencesegmentscreate)
139
150
  - [Workflows.list()](#workflowslist)
151
+ - [Workflows.create()](#workflowscreate)
140
152
  - [Workflows.get()](#workflowsget)
153
+ - [Workflows.update()](#workflowsupdate)
154
+ - [Workflows.delete()](#workflowsdelete)
155
+ - [Workflows.change_mailing_list()](#workflowschange_mailing_list)
141
156
  - [Workflows.get_node()](#workflowsget_node)
157
+ - [Workflows.get_node_metrics()](#workflowsget_node_metrics)
158
+ - [Workflows.create_node()](#workflowscreate_node)
159
+ - [Workflows.update_node()](#workflowsupdate_node)
160
+ - [Workflows.delete_node()](#workflowsdelete_node)
161
+ - [Workflows.add_branch()](#workflowsadd_branch)
162
+ - [Workflows.reroute_node()](#workflowsreroute_node)
163
+ - [Workflows.delete_node_recursive()](#workflowsdelete_node_recursive)
164
+ - [EventPatterns.list()](#eventpatternslist)
165
+ - [EventPatterns.get()](#eventpatternsget)
166
+ - [EventPatterns.get_by_name()](#eventpatternsget_by_name)
142
167
  - [EmailMessages.get()](#emailmessagesget)
143
168
  - [EmailMessages.update()](#emailmessagesupdate)
144
169
  - [EmailMessages.preview()](#emailmessagespreview)
170
+ - [EmailMessages.run_guardian()](#emailmessagesrun_guardian)
145
171
  - [TransactionalGroups.list()](#transactionalgroupslist)
146
172
  - [TransactionalGroups.create()](#transactionalgroupscreate)
147
173
  - [TransactionalGroups.get()](#transactionalgroupsget)
@@ -851,6 +877,21 @@ Get a single transactional email by ID.
851
877
  response = LoopsSdk::Transactional.get(transactional_id: "clfq6dinn000yl70fgwwyp82l")
852
878
  ```
853
879
 
880
+ #### Response
881
+
882
+ ```json
883
+ {
884
+ "id": "cll42l54f20i1la0lfooe3z12",
885
+ "name": "Sign up confirmation",
886
+ "draftEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
887
+ "publishedEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
888
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
889
+ "createdAt": "2025-06-29T07:47:39.370Z",
890
+ "updatedAt": "2025-06-29T07:47:39.370Z",
891
+ "dataVariables": ["confirmationUrl"]
892
+ }
893
+ ```
894
+
854
895
  ---
855
896
 
856
897
  ### Transactional.update()
@@ -878,6 +919,21 @@ response = LoopsSdk::Transactional.update(
878
919
  )
879
920
  ```
880
921
 
922
+ #### Response
923
+
924
+ ```json
925
+ {
926
+ "id": "cll42l54f20i1la0lfooe3z12",
927
+ "name": "Updated name",
928
+ "draftEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
929
+ "publishedEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
930
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
931
+ "createdAt": "2025-06-29T07:47:39.370Z",
932
+ "updatedAt": "2025-06-29T07:47:39.370Z",
933
+ "dataVariables": ["confirmationUrl"]
934
+ }
935
+ ```
936
+
881
937
  ---
882
938
 
883
939
  ### Transactional.ensure_draft()
@@ -898,6 +954,22 @@ Ensure a transactional email has a draft email message. If a draft already exist
898
954
  response = LoopsSdk::Transactional.ensure_draft(transactional_id: "clfq6dinn000yl70fgwwyp82l")
899
955
  ```
900
956
 
957
+ #### Response
958
+
959
+ ```json
960
+ {
961
+ "id": "cll42l54f20i1la0lfooe3z12",
962
+ "name": "Sign up confirmation",
963
+ "draftEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
964
+ "draftEmailMessageContentRevisionId": "clrev1s10n2i3d4e5f6g7h8",
965
+ "publishedEmailMessageId": null,
966
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
967
+ "createdAt": "2025-06-29T07:47:39.370Z",
968
+ "updatedAt": "2025-06-29T07:47:39.370Z",
969
+ "dataVariables": []
970
+ }
971
+ ```
972
+
901
973
  ---
902
974
 
903
975
  ### Transactional.publish()
@@ -918,6 +990,53 @@ Publish a transactional email's current draft. The draft becomes the published v
918
990
  response = LoopsSdk::Transactional.publish(transactional_id: "clfq6dinn000yl70fgwwyp82l")
919
991
  ```
920
992
 
993
+ #### Response
994
+
995
+ ```json
996
+ {
997
+ "id": "cll42l54f20i1la0lfooe3z12",
998
+ "name": "Sign up confirmation",
999
+ "draftEmailMessageId": null,
1000
+ "publishedEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
1001
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
1002
+ "createdAt": "2025-06-29T07:47:39.370Z",
1003
+ "updatedAt": "2025-06-29T07:47:39.370Z",
1004
+ "dataVariables": []
1005
+ }
1006
+ ```
1007
+
1008
+ ---
1009
+
1010
+ ### Transactional.metrics()
1011
+
1012
+ Get all-time delivery metrics for a transactional email.
1013
+
1014
+ [API Reference](https://loops.so/docs/api-reference/get-transactional-email-metrics)
1015
+
1016
+ #### Parameters
1017
+
1018
+ | Name | Type | Required | Notes |
1019
+ | ------------------ | ------ | -------- | ----- |
1020
+ | `transactional_id` | string | Yes | |
1021
+
1022
+ #### Example
1023
+
1024
+ ```ruby
1025
+ response = LoopsSdk::Transactional.metrics(transactional_id: "clfq6dinn000yl70fgwwyp82l")
1026
+ ```
1027
+
1028
+ #### Response
1029
+
1030
+ ```json
1031
+ {
1032
+ "sends": 4210,
1033
+ "deliveries": 4175,
1034
+ "spamReports": 1,
1035
+ "hardBounces": 9,
1036
+ "softBounces": 13
1037
+ }
1038
+ ```
1039
+
921
1040
  ---
922
1041
 
923
1042
  ### Transactional.send()
@@ -1060,6 +1179,31 @@ response = LoopsSdk::Themes.list
1060
1179
  response = LoopsSdk::Themes.list(perPage: 15, cursor: "cursor_value")
1061
1180
  ```
1062
1181
 
1182
+ #### Response
1183
+
1184
+ ```json
1185
+ {
1186
+ "pagination": {
1187
+ "totalResults": 1,
1188
+ "returnedResults": 1,
1189
+ "perPage": 20,
1190
+ "totalPages": 1,
1191
+ "nextCursor": null,
1192
+ "nextPage": null
1193
+ },
1194
+ "data": [
1195
+ {
1196
+ "id": "clo1z5q7s004yl70y3z4a5b6c",
1197
+ "name": "Default",
1198
+ "styles": { "backgroundColor": "#ffffff" },
1199
+ "isDefault": true,
1200
+ "createdAt": "2025-01-01T00:00:00.000Z",
1201
+ "updatedAt": "2025-01-01T00:00:00.000Z"
1202
+ }
1203
+ ]
1204
+ }
1205
+ ```
1206
+
1063
1207
  ---
1064
1208
 
1065
1209
  ### Themes.get()
@@ -1080,6 +1224,100 @@ Get a single theme by ID.
1080
1224
  response = LoopsSdk::Themes.get(theme_id: "clo5p8q0r0132ntx6flkunw89")
1081
1225
  ```
1082
1226
 
1227
+ #### Response
1228
+
1229
+ ```json
1230
+ {
1231
+ "id": "clo1z5q7s004yl70y3z4a5b6c",
1232
+ "name": "Default",
1233
+ "styles": { "backgroundColor": "#ffffff" },
1234
+ "isDefault": true,
1235
+ "createdAt": "2025-01-01T00:00:00.000Z",
1236
+ "updatedAt": "2025-01-01T00:00:00.000Z"
1237
+ }
1238
+ ```
1239
+
1240
+ ---
1241
+
1242
+ ### Themes.create()
1243
+
1244
+ Create a new email theme.
1245
+
1246
+ [API Reference](https://loops.so/docs/api-reference/create-theme)
1247
+
1248
+ #### Parameters
1249
+
1250
+ | Name | Type | Required | Notes |
1251
+ | -------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |
1252
+ | `name` | string | Yes | |
1253
+ | `styles` | object | No | Style attributes matching LMX `<Style />` tag attribute names. See the API reference for the full list. |
1254
+
1255
+ #### Example
1256
+
1257
+ ```ruby
1258
+ response = LoopsSdk::Themes.create(
1259
+ name: "Dark mode",
1260
+ styles: { backgroundColor: "#111827", bodyColor: "#1f2937" }
1261
+ )
1262
+ ```
1263
+
1264
+ #### Response
1265
+
1266
+ ```json
1267
+ {
1268
+ "id": "clt3u5v7w9x1y3z5a7b9c1d3",
1269
+ "name": "Dark mode",
1270
+ "styles": {
1271
+ "backgroundColor": "#111827",
1272
+ "bodyColor": "#1f2937"
1273
+ },
1274
+ "isDefault": false,
1275
+ "createdAt": "2025-06-29T07:47:39.370Z",
1276
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1277
+ }
1278
+ ```
1279
+
1280
+ ---
1281
+
1282
+ ### Themes.update()
1283
+
1284
+ Update a theme's name and/or styles. Style changes cascade to emails using the theme.
1285
+
1286
+ [API Reference](https://loops.so/docs/api-reference/update-theme)
1287
+
1288
+ #### Parameters
1289
+
1290
+ | Name | Type | Required | Notes |
1291
+ | ---------- | ------ | -------- | ----- |
1292
+ | `theme_id` | string | Yes | |
1293
+ | `name` | string | No | |
1294
+ | `styles` | object | No | |
1295
+
1296
+ At least one of `name` or `styles` must be provided.
1297
+
1298
+ #### Example
1299
+
1300
+ ```ruby
1301
+ response = LoopsSdk::Themes.update(
1302
+ theme_id: "clo5p8q0r0132ntx6flkunw89",
1303
+ name: "Updated theme"
1304
+ )
1305
+ ```
1306
+
1307
+ #### Response
1308
+
1309
+ ```json
1310
+ {
1311
+ "id": "clt3u5v7w9x1y3z5a7b9c1d3",
1312
+ "name": "Updated theme",
1313
+ "styles": { "backgroundColor": "#111827" },
1314
+ "isDefault": false,
1315
+ "createdAt": "2025-06-29T07:47:39.370Z",
1316
+ "updatedAt": "2025-06-29T07:47:39.370Z",
1317
+ "affectedEmailCount": 3
1318
+ }
1319
+ ```
1320
+
1083
1321
  ---
1084
1322
 
1085
1323
  ### Components.list()
@@ -1101,6 +1339,28 @@ List email components.
1101
1339
  response = LoopsSdk::Components.list
1102
1340
  ```
1103
1341
 
1342
+ #### Response
1343
+
1344
+ ```json
1345
+ {
1346
+ "pagination": {
1347
+ "totalResults": 1,
1348
+ "returnedResults": 1,
1349
+ "perPage": 20,
1350
+ "totalPages": 1,
1351
+ "nextCursor": null,
1352
+ "nextPage": null
1353
+ },
1354
+ "data": [
1355
+ {
1356
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1357
+ "name": "Header",
1358
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>"
1359
+ }
1360
+ ]
1361
+ }
1362
+ ```
1363
+
1104
1364
  ---
1105
1365
 
1106
1366
  ### Components.get()
@@ -1121,6 +1381,88 @@ Get a single component by ID.
1121
1381
  response = LoopsSdk::Components.get(component_id: "clp6q9r1s0154ouy7gmlovx90")
1122
1382
  ```
1123
1383
 
1384
+ #### Response
1385
+
1386
+ ```json
1387
+ {
1388
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1389
+ "name": "Header",
1390
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>"
1391
+ }
1392
+ ```
1393
+
1394
+ ---
1395
+
1396
+ ### Components.create()
1397
+
1398
+ Create a new email component from an LMX body.
1399
+
1400
+ [API Reference](https://loops.so/docs/api-reference/create-component)
1401
+
1402
+ #### Parameters
1403
+
1404
+ | Name | Type | Required | Notes |
1405
+ | ------ | ------ | -------- | -------------------------------- |
1406
+ | `name` | string | Yes | |
1407
+ | `lmx` | string | Yes | The component body as LMX. |
1408
+
1409
+ #### Example
1410
+
1411
+ ```ruby
1412
+ response = LoopsSdk::Components.create(
1413
+ name: "Header",
1414
+ lmx: "<Paragraph>Welcome to Acme</Paragraph>"
1415
+ )
1416
+ ```
1417
+
1418
+ #### Response
1419
+
1420
+ ```json
1421
+ {
1422
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1423
+ "name": "Header",
1424
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>"
1425
+ }
1426
+ ```
1427
+
1428
+ ---
1429
+
1430
+ ### Components.update()
1431
+
1432
+ Update a component's name and/or LMX body. Body changes cascade to emails using the component.
1433
+
1434
+ [API Reference](https://loops.so/docs/api-reference/update-component)
1435
+
1436
+ #### Parameters
1437
+
1438
+ | Name | Type | Required | Notes |
1439
+ | -------------- | ------ | -------- | ----- |
1440
+ | `component_id` | string | Yes | |
1441
+ | `name` | string | No | |
1442
+ | `lmx` | string | No | |
1443
+
1444
+ At least one of `name` or `lmx` must be provided.
1445
+
1446
+ #### Example
1447
+
1448
+ ```ruby
1449
+ response = LoopsSdk::Components.update(
1450
+ component_id: "clp6q9r1s0154ouy7gmlovx90",
1451
+ name: "Updated Header"
1452
+ )
1453
+ ```
1454
+
1455
+ #### Response
1456
+
1457
+ ```json
1458
+ {
1459
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1460
+ "name": "Updated Header",
1461
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>",
1462
+ "affectedEmailCount": 2
1463
+ }
1464
+ ```
1465
+
1124
1466
  ---
1125
1467
 
1126
1468
  ### Campaigns.list()
@@ -1142,6 +1484,36 @@ List campaigns.
1142
1484
  response = LoopsSdk::Campaigns.list
1143
1485
  ```
1144
1486
 
1487
+ #### Response
1488
+
1489
+ ```json
1490
+ {
1491
+ "pagination": {
1492
+ "totalResults": 1,
1493
+ "returnedResults": 1,
1494
+ "perPage": 20,
1495
+ "totalPages": 1,
1496
+ "nextCursor": null,
1497
+ "nextPage": null
1498
+ },
1499
+ "data": [
1500
+ {
1501
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1502
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1503
+ "name": "Spring announcement",
1504
+ "status": "Draft",
1505
+ "createdAt": "2025-01-01T00:00:00.000Z",
1506
+ "updatedAt": "2025-01-01T00:00:00.000Z",
1507
+ "campaignGroupId": null,
1508
+ "mailingListId": null,
1509
+ "audienceSegmentId": null,
1510
+ "audienceFilter": null,
1511
+ "scheduling": { "method": "now", "timestamp": null }
1512
+ }
1513
+ ]
1514
+ }
1515
+ ```
1516
+
1145
1517
  ---
1146
1518
 
1147
1519
  ### Campaigns.create()
@@ -1173,6 +1545,25 @@ response = LoopsSdk::Campaigns.create(
1173
1545
  )
1174
1546
  ```
1175
1547
 
1548
+ #### Response
1549
+
1550
+ ```json
1551
+ {
1552
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1553
+ "name": "Spring announcement",
1554
+ "status": "Draft",
1555
+ "createdAt": "2025-01-01T00:00:00.000Z",
1556
+ "updatedAt": "2025-01-01T00:00:00.000Z",
1557
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1558
+ "emailMessageContentRevisionId": "clv8g2x4z012yl70n5o6p7q8r",
1559
+ "campaignGroupId": null,
1560
+ "mailingListId": null,
1561
+ "audienceSegmentId": null,
1562
+ "audienceFilter": null,
1563
+ "scheduling": { "method": "now", "timestamp": null }
1564
+ }
1565
+ ```
1566
+
1176
1567
  ---
1177
1568
 
1178
1569
  ### Campaigns.get()
@@ -1193,6 +1584,24 @@ Get a single campaign by ID.
1193
1584
  response = LoopsSdk::Campaigns.get(campaign_id: "cln4o7p9q0110msw5ekjtmv78")
1194
1585
  ```
1195
1586
 
1587
+ #### Response
1588
+
1589
+ ```json
1590
+ {
1591
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1592
+ "name": "Spring announcement",
1593
+ "status": "Draft",
1594
+ "createdAt": "2025-01-01T00:00:00.000Z",
1595
+ "updatedAt": "2025-01-01T00:00:00.000Z",
1596
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1597
+ "campaignGroupId": null,
1598
+ "mailingListId": null,
1599
+ "audienceSegmentId": null,
1600
+ "audienceFilter": null,
1601
+ "scheduling": { "method": "now", "timestamp": null }
1602
+ }
1603
+ ```
1604
+
1196
1605
  ---
1197
1606
 
1198
1607
  ### Campaigns.update()
@@ -1224,6 +1633,58 @@ response = LoopsSdk::Campaigns.update(
1224
1633
  )
1225
1634
  ```
1226
1635
 
1636
+ #### Response
1637
+
1638
+ ```json
1639
+ {
1640
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1641
+ "name": "Updated campaign name",
1642
+ "status": "Draft",
1643
+ "createdAt": "2025-01-01T00:00:00.000Z",
1644
+ "updatedAt": "2025-01-02T00:00:00.000Z",
1645
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1646
+ "campaignGroupId": null,
1647
+ "mailingListId": null,
1648
+ "audienceSegmentId": null,
1649
+ "audienceFilter": null,
1650
+ "scheduling": { "method": "now", "timestamp": null }
1651
+ }
1652
+ ```
1653
+
1654
+ ---
1655
+
1656
+ ### Campaigns.metrics()
1657
+
1658
+ Get all-time engagement metrics for a campaign.
1659
+
1660
+ [API Reference](https://loops.so/docs/api-reference/get-campaign-metrics)
1661
+
1662
+ #### Parameters
1663
+
1664
+ | Name | Type | Required | Notes |
1665
+ | ------------- | ------ | -------- | ----- |
1666
+ | `campaign_id` | string | Yes | |
1667
+
1668
+ #### Example
1669
+
1670
+ ```ruby
1671
+ response = LoopsSdk::Campaigns.metrics(campaign_id: "cln4o7p9q0110msw5ekjtmv78")
1672
+ ```
1673
+
1674
+ #### Response
1675
+
1676
+ ```json
1677
+ {
1678
+ "sends": 4210,
1679
+ "opens": 1922,
1680
+ "clicks": 301,
1681
+ "unsubscribes": 12,
1682
+ "spamReports": 1,
1683
+ "hardBounces": 9,
1684
+ "softBounces": 13
1685
+ }
1686
+ ```
1687
+
1227
1688
  ---
1228
1689
 
1229
1690
  ### EmailMessages.get()
@@ -1244,6 +1705,24 @@ Get an email message, including its LMX content.
1244
1705
  response = LoopsSdk::EmailMessages.get(email_message_id: "cly8k3m0n0044jpx2bghepq45")
1245
1706
  ```
1246
1707
 
1708
+ #### Response
1709
+
1710
+ ```json
1711
+ {
1712
+ "id": "clm9x3o5q002yl70a8b3c4d5e",
1713
+ "campaignId": "cln0y4p6r003yl70i1j2k3l4m",
1714
+ "subject": "Hello",
1715
+ "previewText": "Preview text",
1716
+ "fromName": "Loops",
1717
+ "fromEmail": "hello",
1718
+ "replyToEmail": "",
1719
+ "emailFormat": "styled",
1720
+ "lmx": "<H1>...</H1><Paragraph>...</Paragraph>",
1721
+ "contentRevisionId": "clv8g2x4z012yl70n5o6p7q8r",
1722
+ "updatedAt": "2025-01-01T00:00:00.000Z"
1723
+ }
1724
+ ```
1725
+
1247
1726
  ---
1248
1727
 
1249
1728
  ### EmailMessages.update()
@@ -1282,15 +1761,33 @@ response = LoopsSdk::EmailMessages.update(
1282
1761
  preview_text: "See what's new",
1283
1762
  from_name: "Loops",
1284
1763
  from_email: "hello",
1285
- lmx: "<Email><Style /></Email>"
1764
+ lmx: "<Style /><Paragraph>...</Paragraph>"
1286
1765
  )
1287
1766
  ```
1288
1767
 
1289
- ---
1290
-
1291
- ### EmailMessages.preview()
1292
-
1293
- Send a test preview of an email message to one or more addresses.
1768
+ #### Response
1769
+
1770
+ ```json
1771
+ {
1772
+ "id": "clm9x3o5q002yl70a8b3c4d5e",
1773
+ "campaignId": "cln0y4p6r003yl70i1j2k3l4m",
1774
+ "subject": "Spring announcement",
1775
+ "previewText": "See what's new",
1776
+ "fromName": "Loops",
1777
+ "fromEmail": "hello",
1778
+ "replyToEmail": "",
1779
+ "emailFormat": "styled",
1780
+ "lmx": "<Style /><Paragraph>...</Paragraph>",
1781
+ "contentRevisionId": "clv8g2x4z013yl70s9t0u1v2w",
1782
+ "updatedAt": "2025-01-02T00:00:00.000Z"
1783
+ }
1784
+ ```
1785
+
1786
+ ---
1787
+
1788
+ ### EmailMessages.preview()
1789
+
1790
+ Send a test preview of an email message to one or more addresses.
1294
1791
 
1295
1792
  [API Reference](https://loops.so/docs/api-reference/preview-email-message)
1296
1793
 
@@ -1314,6 +1811,50 @@ response = LoopsSdk::EmailMessages.preview(
1314
1811
  )
1315
1812
  ```
1316
1813
 
1814
+ #### Response
1815
+
1816
+ ```json
1817
+ {
1818
+ "id": "cle5f7g9h1i3j5k7l9m1n3p5"
1819
+ }
1820
+ ```
1821
+
1822
+ ---
1823
+
1824
+ ### EmailMessages.run_guardian()
1825
+
1826
+ Run Guardian content validation on an email message and return errors and warnings.
1827
+
1828
+ [API Reference](https://loops.so/docs/api-reference/run-guardian-checks)
1829
+
1830
+ #### Parameters
1831
+
1832
+ | Name | Type | Required | Notes |
1833
+ | ------------------ | ------ | -------- | ----- |
1834
+ | `email_message_id` | string | Yes | |
1835
+
1836
+ #### Example
1837
+
1838
+ ```ruby
1839
+ response = LoopsSdk::EmailMessages.run_guardian(email_message_id: "cly8k3m0n0044jpx2bghepq45")
1840
+ ```
1841
+
1842
+ #### Response
1843
+
1844
+ ```json
1845
+ {
1846
+ "errors": [
1847
+ {
1848
+ "rule": "missingButtonHrefs",
1849
+ "title": "Missing button link",
1850
+ "description": "Buttons won't work without href value",
1851
+ "items": [{ "label": "Click here" }]
1852
+ }
1853
+ ],
1854
+ "warnings": []
1855
+ }
1856
+ ```
1857
+
1317
1858
  ---
1318
1859
 
1319
1860
  ### CampaignGroups.list()
@@ -1335,6 +1876,30 @@ List campaign groups.
1335
1876
  response = LoopsSdk::CampaignGroups.list
1336
1877
  ```
1337
1878
 
1879
+ #### Response
1880
+
1881
+ ```json
1882
+ {
1883
+ "pagination": {
1884
+ "totalResults": 1,
1885
+ "returnedResults": 1,
1886
+ "perPage": 20,
1887
+ "totalPages": 1,
1888
+ "nextCursor": null,
1889
+ "nextPage": null
1890
+ },
1891
+ "data": [
1892
+ {
1893
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
1894
+ "name": "Newsletters",
1895
+ "description": "Monthly product updates",
1896
+ "createdAt": "2025-06-29T07:47:39.370Z",
1897
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1898
+ }
1899
+ ]
1900
+ }
1901
+ ```
1902
+
1338
1903
  ---
1339
1904
 
1340
1905
  ### CampaignGroups.create()
@@ -1356,6 +1921,18 @@ Create a campaign group.
1356
1921
  response = LoopsSdk::CampaignGroups.create(name: "Newsletters", description: "Monthly updates")
1357
1922
  ```
1358
1923
 
1924
+ #### Response
1925
+
1926
+ ```json
1927
+ {
1928
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
1929
+ "name": "Newsletters",
1930
+ "description": "Monthly updates",
1931
+ "createdAt": "2025-06-29T07:47:39.370Z",
1932
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1933
+ }
1934
+ ```
1935
+
1359
1936
  ---
1360
1937
 
1361
1938
  ### CampaignGroups.get()
@@ -1376,6 +1953,18 @@ Get a campaign group by ID.
1376
1953
  response = LoopsSdk::CampaignGroups.get(campaign_group_id: "clq7r0s2t0176pvz8hnmpwy01")
1377
1954
  ```
1378
1955
 
1956
+ #### Response
1957
+
1958
+ ```json
1959
+ {
1960
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
1961
+ "name": "Newsletters",
1962
+ "description": "Monthly product updates",
1963
+ "createdAt": "2025-06-29T07:47:39.370Z",
1964
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1965
+ }
1966
+ ```
1967
+
1379
1968
  ---
1380
1969
 
1381
1970
  ### CampaignGroups.update()
@@ -1403,13 +1992,25 @@ response = LoopsSdk::CampaignGroups.update(
1403
1992
  )
1404
1993
  ```
1405
1994
 
1995
+ #### Response
1996
+
1997
+ ```json
1998
+ {
1999
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2000
+ "name": "Updated name",
2001
+ "description": "Monthly product updates",
2002
+ "createdAt": "2025-06-29T07:47:39.370Z",
2003
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2004
+ }
2005
+ ```
2006
+
1406
2007
  ---
1407
2008
 
1408
- ### AudienceSegments.list()
2009
+ ### WorkflowGroups.list()
1409
2010
 
1410
- List audience segments.
2011
+ List workflow groups.
1411
2012
 
1412
- [API Reference](https://loops.so/docs/api-reference/list-audience-segments)
2013
+ [API Reference](https://loops.so/docs/api-reference/list-workflow-groups)
1413
2014
 
1414
2015
  #### Parameters
1415
2016
 
@@ -1421,167 +2022,1265 @@ List audience segments.
1421
2022
  #### Example
1422
2023
 
1423
2024
  ```ruby
1424
- response = LoopsSdk::AudienceSegments.list
2025
+ response = LoopsSdk::WorkflowGroups.list
2026
+ ```
2027
+
2028
+ #### Response
2029
+
2030
+ ```json
2031
+ {
2032
+ "pagination": {
2033
+ "totalResults": 1,
2034
+ "returnedResults": 1,
2035
+ "perPage": 20,
2036
+ "totalPages": 1,
2037
+ "nextCursor": null,
2038
+ "nextPage": null
2039
+ },
2040
+ "data": [
2041
+ {
2042
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2043
+ "name": "Onboarding",
2044
+ "description": "Signup and activation flows",
2045
+ "createdAt": "2025-06-29T07:47:39.370Z",
2046
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2047
+ }
2048
+ ]
2049
+ }
1425
2050
  ```
1426
2051
 
1427
2052
  ---
1428
2053
 
1429
- ### AudienceSegments.get()
2054
+ ### WorkflowGroups.create()
1430
2055
 
1431
- Get an audience segment by ID.
2056
+ Create a workflow group.
1432
2057
 
1433
- [API Reference](https://loops.so/docs/api-reference/get-audience-segment)
2058
+ [API Reference](https://loops.so/docs/api-reference/create-workflow-group)
1434
2059
 
1435
2060
  #### Parameters
1436
2061
 
1437
- | Name | Type | Required | Notes |
1438
- | ---- | ------ | -------- | ----- |
1439
- | `audience_segment_id` | string | Yes | |
2062
+ | Name | Type | Required | Notes |
2063
+ | ------------- | ------ | -------- | --------------------------------------- |
2064
+ | `name` | string | Yes | Cannot be the reserved name "Unsorted". |
2065
+ | `description` | string | No | An optional description for the group. |
1440
2066
 
1441
2067
  #### Example
1442
2068
 
1443
2069
  ```ruby
1444
- response = LoopsSdk::AudienceSegments.get(audience_segment_id: "clr8s1t3u0198qw09iotqzx12")
2070
+ response = LoopsSdk::WorkflowGroups.create(name: "Onboarding", description: "Signup flows")
2071
+ ```
2072
+
2073
+ #### Response
2074
+
2075
+ ```json
2076
+ {
2077
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2078
+ "name": "Onboarding",
2079
+ "description": "Signup flows",
2080
+ "createdAt": "2025-06-29T07:47:39.370Z",
2081
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2082
+ }
1445
2083
  ```
1446
2084
 
1447
2085
  ---
1448
2086
 
1449
- ### Workflows.list()
2087
+ ### WorkflowGroups.get()
1450
2088
 
1451
- List workflows.
2089
+ Get a workflow group by ID.
1452
2090
 
1453
- [API Reference](https://loops.so/docs/api-reference/list-workflows)
2091
+ [API Reference](https://loops.so/docs/api-reference/get-workflow-group)
1454
2092
 
1455
2093
  #### Parameters
1456
2094
 
1457
- | Name | Type | Required | Notes |
1458
- | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
1459
- | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
1460
- | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
2095
+ | Name | Type | Required | Notes |
2096
+ | ------------------- | ------ | -------- | ----- |
2097
+ | `workflow_group_id` | string | Yes | |
1461
2098
 
1462
2099
  #### Example
1463
2100
 
1464
2101
  ```ruby
1465
- response = LoopsSdk::Workflows.list
2102
+ response = LoopsSdk::WorkflowGroups.get(workflow_group_id: "clw8x0y2z0300ycc1lrstvw78")
2103
+ ```
2104
+
2105
+ #### Response
2106
+
2107
+ ```json
2108
+ {
2109
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2110
+ "name": "Onboarding",
2111
+ "description": "Signup and activation flows",
2112
+ "createdAt": "2025-06-29T07:47:39.370Z",
2113
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2114
+ }
1466
2115
  ```
1467
2116
 
1468
2117
  ---
1469
2118
 
1470
- ### Workflows.get()
2119
+ ### WorkflowGroups.update()
1471
2120
 
1472
- Get a simplified workflow graph.
2121
+ Update a workflow group's name or description.
1473
2122
 
1474
- [API Reference](https://loops.so/docs/api-reference/get-workflow)
2123
+ [API Reference](https://loops.so/docs/api-reference/update-workflow-group)
1475
2124
 
1476
2125
  #### Parameters
1477
2126
 
1478
- | Name | Type | Required | Notes |
1479
- | ---- | ------ | -------- | ----- |
1480
- | `workflow_id` | string | Yes | |
2127
+ | Name | Type | Required | Notes |
2128
+ | ------------------- | ------ | -------- | --------------------------------------- |
2129
+ | `workflow_group_id` | string | Yes | |
2130
+ | `name` | string | No | Cannot be the reserved name "Unsorted". |
2131
+ | `description` | string | No | |
2132
+
2133
+ At least one field must be provided.
1481
2134
 
1482
2135
  #### Example
1483
2136
 
1484
2137
  ```ruby
1485
- response = LoopsSdk::Workflows.get(workflow_id: "cls9t2u4v0210rx20jpuary23")
2138
+ response = LoopsSdk::WorkflowGroups.update(
2139
+ workflow_group_id: "clw8x0y2z0300ycc1lrstvw78",
2140
+ name: "Updated name"
2141
+ )
2142
+ ```
2143
+
2144
+ #### Response
2145
+
2146
+ ```json
2147
+ {
2148
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2149
+ "name": "Updated name",
2150
+ "description": "Signup and activation flows",
2151
+ "createdAt": "2025-06-29T07:47:39.370Z",
2152
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2153
+ }
1486
2154
  ```
1487
2155
 
1488
2156
  ---
1489
2157
 
1490
- ### Workflows.get_node()
2158
+ ### AudienceSegments.list()
1491
2159
 
1492
- Get detailed data for a single workflow node.
2160
+ List audience segments.
1493
2161
 
1494
- [API Reference](https://loops.so/docs/api-reference/get-workflow-node)
2162
+ [API Reference](https://loops.so/docs/api-reference/list-audience-segments)
1495
2163
 
1496
2164
  #### Parameters
1497
2165
 
1498
- | Name | Type | Required | Notes |
1499
- | ------------ | ------ | -------- | ----- |
1500
- | `workflow_id`| string | Yes | |
1501
- | `node_id` | string | Yes | |
2166
+ | Name | Type | Required | Notes |
2167
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
2168
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
2169
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
1502
2170
 
1503
2171
  #### Example
1504
2172
 
1505
2173
  ```ruby
1506
- response = LoopsSdk::Workflows.get_node(workflow_id: "cls9t2u4v0210rx20jpuary23", node_id: "clt0u3v5w0232sy31kqvbzs34")
2174
+ response = LoopsSdk::AudienceSegments.list
2175
+ ```
2176
+
2177
+ #### Response
2178
+
2179
+ ```json
2180
+ {
2181
+ "pagination": {
2182
+ "totalResults": 1,
2183
+ "returnedResults": 1,
2184
+ "perPage": 20,
2185
+ "totalPages": 1,
2186
+ "nextCursor": null,
2187
+ "nextPage": null
2188
+ },
2189
+ "data": [
2190
+ {
2191
+ "id": "cls6e8g0i2k4m6o8q0s2u4w6",
2192
+ "name": "Power users",
2193
+ "description": "Contacts on the pro plan",
2194
+ "createdAt": "2025-06-29T07:47:39.370Z",
2195
+ "updatedAt": "2025-06-29T07:47:39.370Z",
2196
+ "filter": {
2197
+ "match": "all",
2198
+ "conditions": [
2199
+ { "type": "property", "key": "plan", "operator": "equals", "value": "pro" }
2200
+ ]
2201
+ }
2202
+ }
2203
+ ]
2204
+ }
1507
2205
  ```
1508
2206
 
1509
2207
  ---
1510
2208
 
1511
- ### TransactionalGroups.list()
2209
+ ### AudienceSegments.get()
1512
2210
 
1513
- List transactional groups.
2211
+ Get an audience segment by ID.
1514
2212
 
1515
- [API Reference](https://loops.so/docs/api-reference/list-transactional-groups)
2213
+ [API Reference](https://loops.so/docs/api-reference/get-audience-segment)
1516
2214
 
1517
2215
  #### Parameters
1518
2216
 
1519
- | Name | Type | Required | Notes |
1520
- | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
1521
- | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
1522
- | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
2217
+ | Name | Type | Required | Notes |
2218
+ | ---- | ------ | -------- | ----- |
2219
+ | `audience_segment_id` | string | Yes | |
1523
2220
 
1524
2221
  #### Example
1525
2222
 
1526
2223
  ```ruby
1527
- response = LoopsSdk::TransactionalGroups.list
2224
+ response = LoopsSdk::AudienceSegments.get(audience_segment_id: "clr8s1t3u0198qw09iotqzx12")
2225
+ ```
2226
+
2227
+ #### Response
2228
+
2229
+ ```json
2230
+ {
2231
+ "id": "cls6e8g0i2k4m6o8q0s2u4w6",
2232
+ "name": "Power users",
2233
+ "description": "Contacts on the pro plan",
2234
+ "createdAt": "2025-06-29T07:47:39.370Z",
2235
+ "updatedAt": "2025-06-29T07:47:39.370Z",
2236
+ "filter": {
2237
+ "match": "all",
2238
+ "conditions": [
2239
+ { "type": "property", "key": "plan", "operator": "equals", "value": "pro" }
2240
+ ]
2241
+ }
2242
+ }
1528
2243
  ```
1529
2244
 
1530
2245
  ---
1531
2246
 
1532
- ### TransactionalGroups.create()
2247
+ ### AudienceSegments.create()
1533
2248
 
1534
- Create a transactional group.
2249
+ Create a new audience segment.
1535
2250
 
1536
- [API Reference](https://loops.so/docs/api-reference/create-transactional-group)
2251
+ [API Reference](https://loops.so/docs/api-reference/create-audience-segment)
1537
2252
 
1538
2253
  #### Parameters
1539
2254
 
1540
- | Name | Type | Required | Notes |
1541
- | ------------- | ------ | -------- | --------------------------------------- |
1542
- | `name` | string | Yes | Cannot be the reserved name "Unsorted". |
1543
- | `description` | string | No | An optional description for the group. |
2255
+ | Name | Type | Required | Notes |
2256
+ | ------------- | ------ | -------- | --------------------------------------------------------------------------- |
2257
+ | `name` | string | Yes | Must be unique within the team. |
2258
+ | `filter` | object | Yes | JSON tree of audience conditions with `match` and `conditions`. See the [API reference](https://loops.so/docs/api-reference/create-audience-segment#body-filter). |
2259
+ | `description` | string | No | |
1544
2260
 
1545
2261
  #### Example
1546
2262
 
1547
2263
  ```ruby
1548
- response = LoopsSdk::TransactionalGroups.create(name: "Account emails")
2264
+ response = LoopsSdk::AudienceSegments.create(
2265
+ name: "Power users",
2266
+ filter: {
2267
+ match: "all",
2268
+ conditions: [
2269
+ { type: "property", key: "plan", operator: "equals", value: "pro" }
2270
+ ]
2271
+ }
2272
+ )
2273
+ ```
2274
+
2275
+ #### Response
2276
+
2277
+ ```json
2278
+ {
2279
+ "id": "cls6e8g0i2k4m6o8q0s2u4w6",
2280
+ "name": "Power users",
2281
+ "description": null,
2282
+ "createdAt": "2025-06-29T07:47:39.370Z",
2283
+ "updatedAt": "2025-06-29T07:47:39.370Z",
2284
+ "filter": {
2285
+ "match": "all",
2286
+ "conditions": [
2287
+ { "type": "property", "key": "plan", "operator": "equals", "value": "pro" }
2288
+ ]
2289
+ }
2290
+ }
1549
2291
  ```
1550
2292
 
1551
2293
  ---
1552
2294
 
1553
- ### TransactionalGroups.get()
2295
+ ### Workflows.list()
1554
2296
 
1555
- Get a transactional group by ID.
2297
+ List workflows.
1556
2298
 
1557
- [API Reference](https://loops.so/docs/api-reference/get-transactional-group)
2299
+ [API Reference](https://loops.so/docs/api-reference/list-workflows)
1558
2300
 
1559
2301
  #### Parameters
1560
2302
 
1561
- | Name | Type | Required | Notes |
1562
- | ---- | ------ | -------- | ----- |
1563
- | `transactional_group_id` | string | Yes | |
2303
+ | Name | Type | Required | Notes |
2304
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
2305
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
2306
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
1564
2307
 
1565
2308
  #### Example
1566
2309
 
1567
2310
  ```ruby
1568
- response = LoopsSdk::TransactionalGroups.get(transactional_group_id: "clv2w3x4y0288xbb0kqrsuv67")
2311
+ response = LoopsSdk::Workflows.list
2312
+ ```
2313
+
2314
+ #### Response
2315
+
2316
+ ```json
2317
+ {
2318
+ "pagination": {
2319
+ "totalResults": 1,
2320
+ "returnedResults": 1,
2321
+ "perPage": 20,
2322
+ "totalPages": 1,
2323
+ "nextCursor": null,
2324
+ "nextPage": null
2325
+ },
2326
+ "data": [
2327
+ {
2328
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2329
+ "url": "https://app.loops.so/workflows/clw1a3b5c7d9e1f3g5h7i9j1",
2330
+ "name": "Onboarding",
2331
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2332
+ "createdAt": "2025-06-29T07:47:39.370Z",
2333
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2334
+ }
2335
+ ]
2336
+ }
1569
2337
  ```
1570
2338
 
1571
2339
  ---
1572
2340
 
1573
- ### TransactionalGroups.update()
2341
+ ### Workflows.create()
1574
2342
 
1575
- Update a transactional group's name or description.
2343
+ Create a draft workflow with a blank trigger and exit node.
1576
2344
 
1577
- [API Reference](https://loops.so/docs/api-reference/update-transactional-group)
2345
+ [API Reference](https://loops.so/docs/api-reference/create-workflow)
1578
2346
 
1579
2347
  #### Parameters
1580
2348
 
1581
- | Name | Type | Required | Notes |
1582
- | ------------- | ------ | -------- | --------------------------------------- |
1583
- | `transactional_group_id` | string | Yes | |
1584
- | `name` | string | No | Cannot be the reserved name "Unsorted". |
2349
+ | Name | Type | Required | Notes |
2350
+ | -------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
2351
+ | `name` | string | Yes | |
2352
+ | `description` | string | No | |
2353
+ | `mailing_list_id` | string | No | The mailing list the workflow sends to. |
2354
+ | `workflow_group_id` | string | No | The workflow group to create this workflow in. Defaults to the team's Unsorted group when omitted. |
2355
+
2356
+ #### Example
2357
+
2358
+ ```ruby
2359
+ response = LoopsSdk::Workflows.create(name: "Welcome series")
2360
+ ```
2361
+
2362
+ #### Response
2363
+
2364
+ ```json
2365
+ {
2366
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2367
+ "status": "Draft",
2368
+ "name": "Welcome series",
2369
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2370
+ "mailingListId": null,
2371
+ "rootNodeId": "cf16k73gq014h3mmj5b6jdi9r",
2372
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2373
+ "nodes": {
2374
+ "cf16k73gq014h3mmj5b6jdi9r": {
2375
+ "typeName": "BlankTrigger",
2376
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"]
2377
+ },
2378
+ "cf16k73gq014h3mmj5b4jdifg": {
2379
+ "typeName": "ExitAction",
2380
+ "nextNodeIds": []
2381
+ }
2382
+ }
2383
+ }
2384
+ ```
2385
+
2386
+ ---
2387
+
2388
+ ### Workflows.get()
2389
+
2390
+ Get a simplified workflow graph.
2391
+
2392
+ [API Reference](https://loops.so/docs/api-reference/get-workflow)
2393
+
2394
+ #### Parameters
2395
+
2396
+ | Name | Type | Required | Notes |
2397
+ | ---- | ------ | -------- | ----- |
2398
+ | `workflow_id` | string | Yes | |
2399
+
2400
+ #### Example
2401
+
2402
+ ```ruby
2403
+ response = LoopsSdk::Workflows.get(workflow_id: "cls9t2u4v0210rx20jpuary23")
2404
+ ```
2405
+
2406
+ #### Response
2407
+
2408
+ ```json
2409
+ {
2410
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2411
+ "status": "Draft",
2412
+ "name": "Onboarding",
2413
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2414
+ "mailingListId": null,
2415
+ "rootNodeId": "cf16k73gq014h3mmj5b6jdi9r",
2416
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2417
+ "nodes": {
2418
+ "cf16k73gq014h3mmj5b6jdi9r": {
2419
+ "typeName": "SignupTrigger",
2420
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"]
2421
+ },
2422
+ "cf16k73gq014h3mmj5b4jdifg": {
2423
+ "typeName": "ExitAction",
2424
+ "nextNodeIds": []
2425
+ }
2426
+ }
2427
+ }
2428
+ ```
2429
+
2430
+ ---
2431
+
2432
+ ### Workflows.update()
2433
+
2434
+ Update a workflow's display properties. To change the mailing list, use `Workflows.change_mailing_list()`.
2435
+
2436
+ [API Reference](https://loops.so/docs/api-reference/update-workflow)
2437
+
2438
+ #### Parameters
2439
+
2440
+ | Name | Type | Required | Notes |
2441
+ | ------------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------- |
2442
+ | `workflow_id` | string | Yes | |
2443
+ | `expected_revision_id` | string | Yes | The `workflowRevisionId` from the latest read or mutation. Pass `nil` for older workflows. |
2444
+ | `name` | string | No | |
2445
+ | `description` | string | No | |
2446
+ | `workflow_group_id` | string | No | The workflow group to move this workflow into. |
2447
+
2448
+ At least one of `name`, `description`, or `workflow_group_id` must be provided.
2449
+
2450
+ #### Example
2451
+
2452
+ ```ruby
2453
+ response = LoopsSdk::Workflows.update(
2454
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2455
+ expected_revision_id: "rev_123",
2456
+ name: "Updated name"
2457
+ )
2458
+ ```
2459
+
2460
+ #### Response
2461
+
2462
+ ```json
2463
+ {
2464
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2465
+ "status": "Draft",
2466
+ "name": "Updated name",
2467
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2468
+ "mailingListId": null,
2469
+ "rootNodeId": "cf16k73gq014h3mmj5b6jdi9r",
2470
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2471
+ "nodes": {
2472
+ "cf16k73gq014h3mmj5b6jdi9r": {
2473
+ "typeName": "BlankTrigger",
2474
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"]
2475
+ },
2476
+ "cf16k73gq014h3mmj5b4jdifg": {
2477
+ "typeName": "ExitAction",
2478
+ "nextNodeIds": []
2479
+ }
2480
+ }
2481
+ }
2482
+ ```
2483
+
2484
+ ---
2485
+
2486
+ ### Workflows.delete()
2487
+
2488
+ Delete a workflow. Successful deletion returns no content (`nil`). If the workflow is currently sending or has queued contacts, the API returns `409 Conflict` — retry with `confirm_delete: true` to delete the workflow, stop sending, and cancel queued contacts.
2489
+
2490
+ [API Reference](https://loops.so/docs/api-reference/delete-workflow)
2491
+
2492
+ #### Parameters
2493
+
2494
+ | Name | Type | Required | Notes |
2495
+ | ---------------------- | ------- | -------- | ---------------------------------------------------------------------------------------------- |
2496
+ | `workflow_id` | string | Yes | |
2497
+ | `expected_revision_id` | string | Yes | The `workflowRevisionId` from the latest read or mutation. Pass `nil` for older workflows. |
2498
+ | `confirm_delete` | boolean | No | Set to `true` after a confirmation-required `409` to confirm deleting a sending workflow or a workflow with queued contacts. |
2499
+
2500
+ #### Example
2501
+
2502
+ ```ruby
2503
+ LoopsSdk::Workflows.delete(
2504
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2505
+ expected_revision_id: "rev_123"
2506
+ )
2507
+
2508
+ # After a 409 confirming a sending or queued workflow:
2509
+ LoopsSdk::Workflows.delete(
2510
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2511
+ expected_revision_id: "rev_123",
2512
+ confirm_delete: true
2513
+ )
2514
+ ```
2515
+
2516
+ #### Response
2517
+
2518
+ `nil` (HTTP `204 No Content`)
2519
+
2520
+ ---
2521
+
2522
+ ### Workflows.change_mailing_list()
2523
+
2524
+ Dry run or apply a workflow mailing list change. If queued contacts would be removed, the API returns `"status": "queuedContactsFound"` — retry with `queued_contact_policy: "discard"` to apply.
2525
+
2526
+ [API Reference](https://loops.so/docs/api-reference/change-workflow-mailing-list)
2527
+
2528
+ #### Parameters
2529
+
2530
+ | Name | Type | Required | Notes |
2531
+ | ------------------------ | ------- | -------- | --------------------------------------------------------------------- |
2532
+ | `workflow_id` | string | Yes | |
2533
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2534
+ | `mailing_list_id` | string | Yes | Pass `nil` to clear the mailing list. |
2535
+ | `dry_run` | boolean | No | If `true`, validate without modifying. |
2536
+ | `queued_contact_policy` | string | No | `fail` (default) or `discard`. |
2537
+
2538
+ #### Example
2539
+
2540
+ ```ruby
2541
+ response = LoopsSdk::Workflows.change_mailing_list(
2542
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2543
+ expected_revision_id: "rev_123",
2544
+ mailing_list_id: "cm06f5v0e45nf0ml5754o9cix",
2545
+ dry_run: true
2546
+ )
2547
+ ```
2548
+
2549
+ #### Response
2550
+
2551
+ ```json
2552
+ {
2553
+ "status": "updated",
2554
+ "mailingListId": "cm06f5v0e45nf0ml5754o9cix",
2555
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2556
+ "queuedContactCount": 0,
2557
+ "workflow": {
2558
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2559
+ "status": "Draft",
2560
+ "name": "Welcome series",
2561
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2562
+ "mailingListId": "cm06f5v0e45nf0ml5754o9cix",
2563
+ "rootNodeId": "cln8p0q2r4s6t8u0v2w4x6z8",
2564
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2565
+ "nodes": {
2566
+ "cln8p0q2r4s6t8u0v2w4x6z8": {
2567
+ "typeName": "BlankTrigger",
2568
+ "nextNodeIds": ["cln1a3b5c7d9e1f3g5h7i9j1"]
2569
+ },
2570
+ "cln1a3b5c7d9e1f3g5h7i9j1": {
2571
+ "typeName": "ExitAction",
2572
+ "nextNodeIds": []
2573
+ }
2574
+ }
2575
+ }
2576
+ }
2577
+ ```
2578
+
2579
+ ---
2580
+
2581
+ ### Workflows.get_node()
2582
+
2583
+ Get detailed data for a single workflow node.
2584
+
2585
+ [API Reference](https://loops.so/docs/api-reference/get-workflow-node)
2586
+
2587
+ #### Parameters
2588
+
2589
+ | Name | Type | Required | Notes |
2590
+ | ------------ | ------ | -------- | ----- |
2591
+ | `workflow_id`| string | Yes | |
2592
+ | `node_id` | string | Yes | |
2593
+
2594
+ #### Example
2595
+
2596
+ ```ruby
2597
+ response = LoopsSdk::Workflows.get_node(workflow_id: "cls9t2u4v0210rx20jpuary23", node_id: "clt0u3v5w0232sy31kqvbzs34")
2598
+ ```
2599
+
2600
+ #### Response
2601
+
2602
+ ```json
2603
+ {
2604
+ "id": "cln8p0q2r4s6t8u0v2w4x6z8",
2605
+ "workflowId": "clw1a3b5c7d9e1f3g5h7i9j1",
2606
+ "typeName": "TimerAction",
2607
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"],
2608
+ "amount": 1,
2609
+ "unit": "h",
2610
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6"
2611
+ }
2612
+ ```
2613
+
2614
+ ---
2615
+
2616
+ ### Workflows.get_node_metrics()
2617
+
2618
+ Get all-time engagement metrics for a workflow email node.
2619
+
2620
+ [API Reference](https://loops.so/docs/api-reference/get-workflow-node-metrics)
2621
+
2622
+ #### Parameters
2623
+
2624
+ | Name | Type | Required | Notes |
2625
+ | ------------- | ------ | -------- | ----- |
2626
+ | `workflow_id` | string | Yes | |
2627
+ | `node_id` | string | Yes | |
2628
+
2629
+ #### Example
2630
+
2631
+ ```ruby
2632
+ response = LoopsSdk::Workflows.get_node_metrics(
2633
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2634
+ node_id: "clt0u3v5w0232sy31kqvbzs34"
2635
+ )
2636
+ ```
2637
+
2638
+ #### Response
2639
+
2640
+ ```json
2641
+ {
2642
+ "sends": 4210,
2643
+ "opens": 1922,
2644
+ "clicks": 301,
2645
+ "unsubscribes": 12,
2646
+ "spamReports": 1,
2647
+ "hardBounces": 9,
2648
+ "softBounces": 13
2649
+ }
2650
+ ```
2651
+
2652
+ ---
2653
+
2654
+ ### Workflows.create_node()
2655
+
2656
+ Create a new default workflow node.
2657
+
2658
+ Use `insert_mode` to choose placement:
2659
+ - `between` — insert between `from_node_id` and `to_node_id`
2660
+ - `before` — insert before `to_node_id`
2661
+ - `after` — insert after `from_node_id` (source must have exactly one outgoing connection)
2662
+
2663
+ [API Reference](https://loops.so/docs/api-reference/create-workflow-node)
2664
+
2665
+ #### Parameters
2666
+
2667
+ | Name | Type | Required | Notes |
2668
+ | ---------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
2669
+ | `workflow_id` | string | Yes | |
2670
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2671
+ | `insert_mode` | string | Yes | `between`, `before`, or `after`. |
2672
+ | `node_type_name` | string | Yes | One of `AudienceFilter`, `BranchNode`, `ExperimentBranchNode`, `TimerAction`, `SendEmailAction`, `VariantNode`. |
2673
+ | `from_node_id` | string | Cond. | Required for `between` and `after`. Not permitted for `before`. |
2674
+ | `to_node_id` | string | Cond. | Required for `between`. For `before`, provide this or `before_node_id` (not both). Not permitted for `after`. |
2675
+ | `before_node_id` | string | Cond. | Deprecated. For `before`, provide this or `to_node_id` (not both). |
2676
+
2677
+ #### Example
2678
+
2679
+ ```ruby
2680
+ response = LoopsSdk::Workflows.create_node(
2681
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2682
+ expected_revision_id: "rev_123",
2683
+ insert_mode: "between",
2684
+ node_type_name: "TimerAction",
2685
+ from_node_id: "node_a",
2686
+ to_node_id: "node_b"
2687
+ )
2688
+
2689
+ response = LoopsSdk::Workflows.create_node(
2690
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2691
+ expected_revision_id: "rev_123",
2692
+ insert_mode: "after",
2693
+ node_type_name: "TimerAction",
2694
+ from_node_id: "node_a"
2695
+ )
2696
+
2697
+ response = LoopsSdk::Workflows.create_node(
2698
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2699
+ expected_revision_id: "rev_123",
2700
+ insert_mode: "before",
2701
+ node_type_name: "TimerAction",
2702
+ to_node_id: "node_b"
2703
+ )
2704
+ ```
2705
+
2706
+ #### Response
2707
+
2708
+ ```json
2709
+ {
2710
+ "node": {
2711
+ "id": "cln8p0q2r4s6t8u0v2w4x6z8",
2712
+ "typeName": "TimerAction",
2713
+ "nextNodeIds": ["node_b"],
2714
+ "amount": 0,
2715
+ "unit": "m",
2716
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6"
2717
+ },
2718
+ "workflow": {
2719
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2720
+ "status": "Draft",
2721
+ "name": "Welcome series",
2722
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2723
+ "mailingListId": null,
2724
+ "rootNodeId": "node_a",
2725
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2726
+ "nodes": {
2727
+ "node_a": {
2728
+ "typeName": "BlankTrigger",
2729
+ "nextNodeIds": ["cln8p0q2r4s6t8u0v2w4x6z8"]
2730
+ },
2731
+ "cln8p0q2r4s6t8u0v2w4x6z8": {
2732
+ "typeName": "TimerAction",
2733
+ "nextNodeIds": ["node_b"],
2734
+ "amount": 0,
2735
+ "unit": "m"
2736
+ },
2737
+ "node_b": {
2738
+ "typeName": "ExitAction",
2739
+ "nextNodeIds": []
2740
+ }
2741
+ }
2742
+ }
2743
+ }
2744
+ ```
2745
+
2746
+ ---
2747
+
2748
+ ### Workflows.update_node()
2749
+
2750
+ Update workflow-node-owned fields for a single node.
2751
+
2752
+ [API Reference](https://loops.so/docs/api-reference/update-workflow-node)
2753
+
2754
+ #### Parameters
2755
+
2756
+ | Name | Type | Required | Notes |
2757
+ | ---------------------- | ------ | -------- | ---------------------------------------------------------- |
2758
+ | `workflow_id` | string | Yes | |
2759
+ | `node_id` | string | Yes | |
2760
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2761
+ | `payload` | object | Yes | Node-type-specific fields. See the API reference. |
2762
+
2763
+ #### Example
2764
+
2765
+ ```ruby
2766
+ response = LoopsSdk::Workflows.update_node(
2767
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2768
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2769
+ expected_revision_id: "rev_123",
2770
+ payload: { amount: 2, unit: "d" }
2771
+ )
2772
+ ```
2773
+
2774
+ #### Response
2775
+
2776
+ ```json
2777
+ {
2778
+ "id": "cln8p0q2r4s6t8u0v2w4x6z8",
2779
+ "workflowId": "clw1a3b5c7d9e1f3g5h7i9j1",
2780
+ "typeName": "TimerAction",
2781
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"],
2782
+ "amount": 2,
2783
+ "unit": "d",
2784
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2785
+ "workflow": {
2786
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2787
+ "status": "Draft",
2788
+ "name": "Welcome series",
2789
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2790
+ "mailingListId": null,
2791
+ "rootNodeId": "cln8p0q2r4s6t8u0v2w4x6z8",
2792
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2793
+ "nodes": {
2794
+ "cln8p0q2r4s6t8u0v2w4x6z8": {
2795
+ "typeName": "TimerAction",
2796
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"],
2797
+ "amount": 2,
2798
+ "unit": "d"
2799
+ },
2800
+ "cf16k73gq014h3mmj5b4jdifg": {
2801
+ "typeName": "ExitAction",
2802
+ "nextNodeIds": []
2803
+ }
2804
+ }
2805
+ }
2806
+ }
2807
+ ```
2808
+
2809
+ ---
2810
+
2811
+ ### Workflows.delete_node()
2812
+
2813
+ Delete a single workflow node. If contacts are queued, the API returns `"status": "queuedContactsFound"` — retry with `queued_contact_policy: "discard"` to delete.
2814
+
2815
+ [API Reference](https://loops.so/docs/api-reference/delete-workflow-node)
2816
+
2817
+ #### Parameters
2818
+
2819
+ | Name | Type | Required | Notes |
2820
+ | ----------------------- | ------- | -------- | ------------------------------ |
2821
+ | `workflow_id` | string | Yes | |
2822
+ | `node_id` | string | Yes | |
2823
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows.|
2824
+ | `dry_run` | boolean | No | |
2825
+ | `queued_contact_policy` | string | No | `fail` (default) or `discard`. |
2826
+
2827
+ #### Example
2828
+
2829
+ ```ruby
2830
+ response = LoopsSdk::Workflows.delete_node(
2831
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2832
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2833
+ expected_revision_id: "rev_123",
2834
+ dry_run: true
2835
+ )
2836
+ ```
2837
+
2838
+ #### Response
2839
+
2840
+ ```json
2841
+ {
2842
+ "status": "deleted",
2843
+ "nodeIds": ["clt0u3v5w0232sy31kqvbzs34"],
2844
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2845
+ "queuedContactCount": 0,
2846
+ "workflow": {
2847
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2848
+ "status": "Draft",
2849
+ "name": "Welcome series",
2850
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2851
+ "mailingListId": null,
2852
+ "rootNodeId": "cln8p0q2r4s6t8u0v2w4x6z8",
2853
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2854
+ "nodes": {
2855
+ "cln8p0q2r4s6t8u0v2w4x6z8": {
2856
+ "typeName": "BlankTrigger",
2857
+ "nextNodeIds": ["cln1a3b5c7d9e1f3g5h7i9j1"]
2858
+ },
2859
+ "cln1a3b5c7d9e1f3g5h7i9j1": {
2860
+ "typeName": "ExitAction",
2861
+ "nextNodeIds": []
2862
+ }
2863
+ }
2864
+ }
2865
+ }
2866
+ ```
2867
+
2868
+ ---
2869
+
2870
+ ### Workflows.add_branch()
2871
+
2872
+ Add a branch and child node under an existing `BranchNode` or `ExperimentBranchNode`.
2873
+
2874
+ [API Reference](https://loops.so/docs/api-reference/add-workflow-branch)
2875
+
2876
+ #### Parameters
2877
+
2878
+ | Name | Type | Required | Notes |
2879
+ | ---------------------- | ------ | -------- | ------------------------------- |
2880
+ | `workflow_id` | string | Yes | |
2881
+ | `node_id` | string | Yes | The branch or experiment node. |
2882
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2883
+
2884
+ #### Example
2885
+
2886
+ ```ruby
2887
+ response = LoopsSdk::Workflows.add_branch(
2888
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2889
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2890
+ expected_revision_id: "rev_123"
2891
+ )
2892
+ ```
2893
+
2894
+ #### Response
2895
+
2896
+ ```json
2897
+ {
2898
+ "node": {
2899
+ "id": "cln0a2b4c6d8e0f2g4h6i8j0",
2900
+ "typeName": "AudienceFilter",
2901
+ "nextNodeIds": [],
2902
+ "appliesDownstream": false,
2903
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6"
2904
+ },
2905
+ "workflow": {
2906
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2907
+ "status": "Draft",
2908
+ "name": "Welcome series",
2909
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2910
+ "mailingListId": null,
2911
+ "rootNodeId": "clt0u3v5w0232sy31kqvbzs34",
2912
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2913
+ "nodes": {
2914
+ "clt0u3v5w0232sy31kqvbzs34": {
2915
+ "typeName": "BranchNode",
2916
+ "nextNodeIds": ["cln0a2b4c6d8e0f2g4h6i8j0"]
2917
+ },
2918
+ "cln0a2b4c6d8e0f2g4h6i8j0": {
2919
+ "typeName": "AudienceFilter",
2920
+ "nextNodeIds": []
2921
+ }
2922
+ }
2923
+ }
2924
+ }
2925
+ ```
2926
+
2927
+ ---
2928
+
2929
+ ### Workflows.reroute_node()
2930
+
2931
+ Reroute a source node's outgoing connection to another valid target node. The source node must have exactly one outgoing connection. Branch and experiment branch nodes cannot be rerouted with this endpoint.
2932
+
2933
+ [API Reference](https://loops.so/docs/api-reference/reroute-node-connection)
2934
+
2935
+ #### Parameters
2936
+
2937
+ | Name | Type | Required | Notes |
2938
+ | ---------------------- | ------ | -------- | ------------------------------------------ |
2939
+ | `workflow_id` | string | Yes | |
2940
+ | `node_id` | string | Yes | The source node whose connection to move. |
2941
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2942
+ | `new_target_node_id` | string | Yes | The node that should receive the connection. |
2943
+
2944
+ #### Example
2945
+
2946
+ ```ruby
2947
+ response = LoopsSdk::Workflows.reroute_node(
2948
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2949
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2950
+ expected_revision_id: "rev_123",
2951
+ new_target_node_id: "cln3c5d7e9f1g3h5i7j9k1l3"
2952
+ )
2953
+ ```
2954
+
2955
+ #### Response
2956
+
2957
+ ```json
2958
+ {
2959
+ "id": "cln8p0q2r4s6t8u0v2w4x6z8",
2960
+ "typeName": "SignupTrigger",
2961
+ "nextNodeIds": ["cln3c5d7e9f1g3h5i7j9k1l3"],
2962
+ "workflowRevisionId": "clx7a3b5c7d9e1f3g5h7i9j2",
2963
+ "workflow": {
2964
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2965
+ "workflowRevisionId": "clx7a3b5c7d9e1f3g5h7i9j2",
2966
+ "status": "Draft",
2967
+ "name": "Welcome series",
2968
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
2969
+ "mailingListId": "clm2k8j4h6g0f8d6s4a2b0z8",
2970
+ "rootNodeId": "cln8p0q2r4s6t8u0v2w4x6z8",
2971
+ "nodes": {
2972
+ "cln8p0q2r4s6t8u0v2w4x6z8": {
2973
+ "typeName": "SignupTrigger",
2974
+ "nextNodeIds": ["cln3c5d7e9f1g3h5i7j9k1l3"]
2975
+ },
2976
+ "cln3c5d7e9f1g3h5i7j9k1l3": {
2977
+ "typeName": "SendEmailAction",
2978
+ "nextNodeIds": [],
2979
+ "emailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
2980
+ "subject": "Welcome to Acme!"
2981
+ }
2982
+ }
2983
+ }
2984
+ }
2985
+ ```
2986
+
2987
+ ---
2988
+
2989
+ ### Workflows.delete_node_recursive()
2990
+
2991
+ Delete a node and its downstream subtree.
2992
+
2993
+ [API Reference](https://loops.so/docs/api-reference/delete-workflow-nodes)
2994
+
2995
+ #### Parameters
2996
+
2997
+ | Name | Type | Required | Notes |
2998
+ | ----------------------- | ------- | -------- | ------------------------------ |
2999
+ | `workflow_id` | string | Yes | |
3000
+ | `node_id` | string | Yes | Root of the subtree to delete. |
3001
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows.|
3002
+ | `dry_run` | boolean | No | |
3003
+ | `queued_contact_policy` | string | No | `fail` (default) or `discard`. |
3004
+
3005
+ #### Example
3006
+
3007
+ ```ruby
3008
+ response = LoopsSdk::Workflows.delete_node_recursive(
3009
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
3010
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
3011
+ expected_revision_id: "rev_123",
3012
+ queued_contact_policy: "discard"
3013
+ )
3014
+ ```
3015
+
3016
+ #### Response
3017
+
3018
+ ```json
3019
+ {
3020
+ "status": "deleted",
3021
+ "nodeIds": [
3022
+ "clt0u3v5w0232sy31kqvbzs34",
3023
+ "cln9q1r3s5t7u9v1w3x5y7z9"
3024
+ ],
3025
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
3026
+ "queuedContactCount": 0,
3027
+ "workflow": {
3028
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
3029
+ "status": "Draft",
3030
+ "name": "Welcome series",
3031
+ "workflowGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
3032
+ "mailingListId": null,
3033
+ "rootNodeId": "cln8p0q2r4s6t8u0v2w4x6z8",
3034
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
3035
+ "nodes": {
3036
+ "cln8p0q2r4s6t8u0v2w4x6z8": {
3037
+ "typeName": "BlankTrigger",
3038
+ "nextNodeIds": ["cln1a3b5c7d9e1f3g5h7i9j1"]
3039
+ },
3040
+ "cln1a3b5c7d9e1f3g5h7i9j1": {
3041
+ "typeName": "ExitAction",
3042
+ "nextNodeIds": []
3043
+ }
3044
+ }
3045
+ }
3046
+ }
3047
+ ```
3048
+
3049
+ ---
3050
+
3051
+ ### EventPatterns.list()
3052
+
3053
+ List event patterns available to workflow event trigger nodes.
3054
+
3055
+ [API Reference](https://loops.so/docs/api-reference/list-event-patterns)
3056
+
3057
+ #### Parameters
3058
+
3059
+ | Name | Type | Required | Notes |
3060
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
3061
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
3062
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
3063
+
3064
+ #### Example
3065
+
3066
+ ```ruby
3067
+ response = LoopsSdk::EventPatterns.list
3068
+ ```
3069
+
3070
+ #### Response
3071
+
3072
+ ```json
3073
+ {
3074
+ "pagination": {
3075
+ "totalResults": 1,
3076
+ "returnedResults": 1,
3077
+ "perPage": 20,
3078
+ "totalPages": 1,
3079
+ "nextCursor": null,
3080
+ "nextPage": null
3081
+ },
3082
+ "data": [
3083
+ {
3084
+ "id": "cle1a2b3c004yl70d5e6f7g8h",
3085
+ "eventName": "signup",
3086
+ "incomingWebhookPlatform": null
3087
+ }
3088
+ ]
3089
+ }
3090
+ ```
3091
+
3092
+ ---
3093
+
3094
+ ### EventPatterns.get()
3095
+
3096
+ Get an event pattern by ID.
3097
+
3098
+ [API Reference](https://loops.so/docs/api-reference/get-event-pattern)
3099
+
3100
+ #### Parameters
3101
+
3102
+ | Name | Type | Required | Notes |
3103
+ | ------------------ | ------ | -------- | ----- |
3104
+ | `event_pattern_id` | string | Yes | |
3105
+
3106
+ #### Example
3107
+
3108
+ ```ruby
3109
+ response = LoopsSdk::EventPatterns.get(event_pattern_id: "cle1v2e3n4t5p6a7t8t9e0r1")
3110
+ ```
3111
+
3112
+ #### Response
3113
+
3114
+ ```json
3115
+ {
3116
+ "id": "cle1a2b3c004yl70d5e6f7g8h",
3117
+ "eventName": "signup",
3118
+ "eventProperties": [
3119
+ { "name": "plan", "type": "string" },
3120
+ { "name": "trialDays", "type": "number" }
3121
+ ],
3122
+ "incomingWebhookPlatform": null
3123
+ }
3124
+ ```
3125
+
3126
+ ---
3127
+
3128
+ ### EventPatterns.get_by_name()
3129
+
3130
+ Get an event pattern by event name. Event names are case-sensitive.
3131
+
3132
+ [API Reference](https://loops.so/docs/api-reference/get-event-pattern-by-name)
3133
+
3134
+ #### Parameters
3135
+
3136
+ | Name | Type | Required | Notes |
3137
+ | ------------ | ------ | -------- | ----- |
3138
+ | `event_name` | string | Yes | |
3139
+
3140
+ #### Example
3141
+
3142
+ ```ruby
3143
+ response = LoopsSdk::EventPatterns.get_by_name(event_name: "signup")
3144
+ ```
3145
+
3146
+ #### Response
3147
+
3148
+ ```json
3149
+ {
3150
+ "id": "cle1a2b3c004yl70d5e6f7g8h",
3151
+ "eventName": "signup",
3152
+ "eventProperties": [
3153
+ { "name": "plan", "type": "string" },
3154
+ { "name": "trialDays", "type": "number" }
3155
+ ],
3156
+ "incomingWebhookPlatform": null
3157
+ }
3158
+ ```
3159
+
3160
+ ---
3161
+
3162
+ ### TransactionalGroups.list()
3163
+
3164
+ List transactional groups.
3165
+
3166
+ [API Reference](https://loops.so/docs/api-reference/list-transactional-groups)
3167
+
3168
+ #### Parameters
3169
+
3170
+ | Name | Type | Required | Notes |
3171
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
3172
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
3173
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
3174
+
3175
+ #### Example
3176
+
3177
+ ```ruby
3178
+ response = LoopsSdk::TransactionalGroups.list
3179
+ ```
3180
+
3181
+ #### Response
3182
+
3183
+ ```json
3184
+ {
3185
+ "pagination": {
3186
+ "totalResults": 1,
3187
+ "returnedResults": 1,
3188
+ "perPage": 20,
3189
+ "totalPages": 1,
3190
+ "nextCursor": null,
3191
+ "nextPage": null
3192
+ },
3193
+ "data": [
3194
+ {
3195
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
3196
+ "name": "Onboarding",
3197
+ "description": "Top of funnel campaigns",
3198
+ "createdAt": "2025-06-29T07:47:39.370Z",
3199
+ "updatedAt": "2025-06-29T07:47:39.370Z"
3200
+ }
3201
+ ]
3202
+ }
3203
+ ```
3204
+
3205
+ ---
3206
+
3207
+ ### TransactionalGroups.create()
3208
+
3209
+ Create a transactional group.
3210
+
3211
+ [API Reference](https://loops.so/docs/api-reference/create-transactional-group)
3212
+
3213
+ #### Parameters
3214
+
3215
+ | Name | Type | Required | Notes |
3216
+ | ------------- | ------ | -------- | --------------------------------------- |
3217
+ | `name` | string | Yes | Cannot be the reserved name "Unsorted". |
3218
+ | `description` | string | No | An optional description for the group. |
3219
+
3220
+ #### Example
3221
+
3222
+ ```ruby
3223
+ response = LoopsSdk::TransactionalGroups.create(name: "Account emails")
3224
+ ```
3225
+
3226
+ #### Response
3227
+
3228
+ ```json
3229
+ {
3230
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
3231
+ "name": "Account emails",
3232
+ "description": "",
3233
+ "createdAt": "2025-06-29T07:47:39.370Z",
3234
+ "updatedAt": "2025-06-29T07:47:39.370Z"
3235
+ }
3236
+ ```
3237
+
3238
+ ---
3239
+
3240
+ ### TransactionalGroups.get()
3241
+
3242
+ Get a transactional group by ID.
3243
+
3244
+ [API Reference](https://loops.so/docs/api-reference/get-transactional-group)
3245
+
3246
+ #### Parameters
3247
+
3248
+ | Name | Type | Required | Notes |
3249
+ | ---- | ------ | -------- | ----- |
3250
+ | `transactional_group_id` | string | Yes | |
3251
+
3252
+ #### Example
3253
+
3254
+ ```ruby
3255
+ response = LoopsSdk::TransactionalGroups.get(transactional_group_id: "clv2w3x4y0288xbb0kqrsuv67")
3256
+ ```
3257
+
3258
+ #### Response
3259
+
3260
+ ```json
3261
+ {
3262
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
3263
+ "name": "Account emails",
3264
+ "description": "",
3265
+ "createdAt": "2025-06-29T07:47:39.370Z",
3266
+ "updatedAt": "2025-06-29T07:47:39.370Z"
3267
+ }
3268
+ ```
3269
+
3270
+ ---
3271
+
3272
+ ### TransactionalGroups.update()
3273
+
3274
+ Update a transactional group's name or description.
3275
+
3276
+ [API Reference](https://loops.so/docs/api-reference/update-transactional-group)
3277
+
3278
+ #### Parameters
3279
+
3280
+ | Name | Type | Required | Notes |
3281
+ | ------------- | ------ | -------- | --------------------------------------- |
3282
+ | `transactional_group_id` | string | Yes | |
3283
+ | `name` | string | No | Cannot be the reserved name "Unsorted". |
1585
3284
  | `description` | string | No | |
1586
3285
 
1587
3286
  At least one field must be provided.
@@ -1595,6 +3294,18 @@ response = LoopsSdk::TransactionalGroups.update(
1595
3294
  )
1596
3295
  ```
1597
3296
 
3297
+ #### Response
3298
+
3299
+ ```json
3300
+ {
3301
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
3302
+ "name": "Updated name",
3303
+ "description": "",
3304
+ "createdAt": "2025-06-29T07:47:39.370Z",
3305
+ "updatedAt": "2025-06-29T07:47:39.370Z"
3306
+ }
3307
+ ```
3308
+
1598
3309
  ---
1599
3310
 
1600
3311
  ### Uploads.upload()