loops_sdk 2.3.0 → 2.4.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
@@ -124,8 +124,12 @@ You can use custom contact properties in API calls. Please make sure to [add cus
124
124
  - [DedicatedSendingIps.list()](#dedicatedsendingipslist)
125
125
  - [Themes.list()](#themeslist)
126
126
  - [Themes.get()](#themesget)
127
+ - [Themes.create()](#themescreate)
128
+ - [Themes.update()](#themesupdate)
127
129
  - [Components.list()](#componentslist)
128
130
  - [Components.get()](#componentsget)
131
+ - [Components.create()](#componentscreate)
132
+ - [Components.update()](#componentsupdate)
129
133
  - [Campaigns.list()](#campaignslist)
130
134
  - [Campaigns.create()](#campaignscreate)
131
135
  - [Campaigns.get()](#campaignsget)
@@ -136,12 +140,25 @@ You can use custom contact properties in API calls. Please make sure to [add cus
136
140
  - [CampaignGroups.update()](#campaigngroupsupdate)
137
141
  - [AudienceSegments.list()](#audiencesegmentslist)
138
142
  - [AudienceSegments.get()](#audiencesegmentsget)
143
+ - [AudienceSegments.create()](#audiencesegmentscreate)
139
144
  - [Workflows.list()](#workflowslist)
145
+ - [Workflows.create()](#workflowscreate)
140
146
  - [Workflows.get()](#workflowsget)
147
+ - [Workflows.update()](#workflowsupdate)
148
+ - [Workflows.change_mailing_list()](#workflowschange_mailing_list)
141
149
  - [Workflows.get_node()](#workflowsget_node)
150
+ - [Workflows.create_node()](#workflowscreate_node)
151
+ - [Workflows.update_node()](#workflowsupdate_node)
152
+ - [Workflows.delete_node()](#workflowsdelete_node)
153
+ - [Workflows.add_branch()](#workflowsadd_branch)
154
+ - [Workflows.delete_node_recursive()](#workflowsdelete_node_recursive)
155
+ - [EventPatterns.list()](#eventpatternslist)
156
+ - [EventPatterns.get()](#eventpatternsget)
157
+ - [EventPatterns.get_by_name()](#eventpatternsget_by_name)
142
158
  - [EmailMessages.get()](#emailmessagesget)
143
159
  - [EmailMessages.update()](#emailmessagesupdate)
144
160
  - [EmailMessages.preview()](#emailmessagespreview)
161
+ - [EmailMessages.run_guardian()](#emailmessagesrun_guardian)
145
162
  - [TransactionalGroups.list()](#transactionalgroupslist)
146
163
  - [TransactionalGroups.create()](#transactionalgroupscreate)
147
164
  - [TransactionalGroups.get()](#transactionalgroupsget)
@@ -851,6 +868,21 @@ Get a single transactional email by ID.
851
868
  response = LoopsSdk::Transactional.get(transactional_id: "clfq6dinn000yl70fgwwyp82l")
852
869
  ```
853
870
 
871
+ #### Response
872
+
873
+ ```json
874
+ {
875
+ "id": "cll42l54f20i1la0lfooe3z12",
876
+ "name": "Sign up confirmation",
877
+ "draftEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
878
+ "publishedEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
879
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
880
+ "createdAt": "2025-06-29T07:47:39.370Z",
881
+ "updatedAt": "2025-06-29T07:47:39.370Z",
882
+ "dataVariables": ["confirmationUrl"]
883
+ }
884
+ ```
885
+
854
886
  ---
855
887
 
856
888
  ### Transactional.update()
@@ -878,6 +910,21 @@ response = LoopsSdk::Transactional.update(
878
910
  )
879
911
  ```
880
912
 
913
+ #### Response
914
+
915
+ ```json
916
+ {
917
+ "id": "cll42l54f20i1la0lfooe3z12",
918
+ "name": "Updated name",
919
+ "draftEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
920
+ "publishedEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
921
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
922
+ "createdAt": "2025-06-29T07:47:39.370Z",
923
+ "updatedAt": "2025-06-29T07:47:39.370Z",
924
+ "dataVariables": ["confirmationUrl"]
925
+ }
926
+ ```
927
+
881
928
  ---
882
929
 
883
930
  ### Transactional.ensure_draft()
@@ -898,6 +945,22 @@ Ensure a transactional email has a draft email message. If a draft already exist
898
945
  response = LoopsSdk::Transactional.ensure_draft(transactional_id: "clfq6dinn000yl70fgwwyp82l")
899
946
  ```
900
947
 
948
+ #### Response
949
+
950
+ ```json
951
+ {
952
+ "id": "cll42l54f20i1la0lfooe3z12",
953
+ "name": "Sign up confirmation",
954
+ "draftEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
955
+ "draftEmailMessageContentRevisionId": "clrev1s10n2i3d4e5f6g7h8",
956
+ "publishedEmailMessageId": null,
957
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
958
+ "createdAt": "2025-06-29T07:47:39.370Z",
959
+ "updatedAt": "2025-06-29T07:47:39.370Z",
960
+ "dataVariables": []
961
+ }
962
+ ```
963
+
901
964
  ---
902
965
 
903
966
  ### Transactional.publish()
@@ -918,6 +981,21 @@ Publish a transactional email's current draft. The draft becomes the published v
918
981
  response = LoopsSdk::Transactional.publish(transactional_id: "clfq6dinn000yl70fgwwyp82l")
919
982
  ```
920
983
 
984
+ #### Response
985
+
986
+ ```json
987
+ {
988
+ "id": "cll42l54f20i1la0lfooe3z12",
989
+ "name": "Sign up confirmation",
990
+ "draftEmailMessageId": null,
991
+ "publishedEmailMessageId": "cle5f7g9h1i3j5k7l9m1n3p5",
992
+ "transactionalGroupId": "clg7n5p3q1r9s7t5u3v1w9y7",
993
+ "createdAt": "2025-06-29T07:47:39.370Z",
994
+ "updatedAt": "2025-06-29T07:47:39.370Z",
995
+ "dataVariables": []
996
+ }
997
+ ```
998
+
921
999
  ---
922
1000
 
923
1001
  ### Transactional.send()
@@ -1060,6 +1138,31 @@ response = LoopsSdk::Themes.list
1060
1138
  response = LoopsSdk::Themes.list(perPage: 15, cursor: "cursor_value")
1061
1139
  ```
1062
1140
 
1141
+ #### Response
1142
+
1143
+ ```json
1144
+ {
1145
+ "pagination": {
1146
+ "totalResults": 1,
1147
+ "returnedResults": 1,
1148
+ "perPage": 20,
1149
+ "totalPages": 1,
1150
+ "nextCursor": null,
1151
+ "nextPage": null
1152
+ },
1153
+ "data": [
1154
+ {
1155
+ "id": "clo1z5q7s004yl70y3z4a5b6c",
1156
+ "name": "Default",
1157
+ "styles": { "backgroundColor": "#ffffff" },
1158
+ "isDefault": true,
1159
+ "createdAt": "2025-01-01T00:00:00.000Z",
1160
+ "updatedAt": "2025-01-01T00:00:00.000Z"
1161
+ }
1162
+ ]
1163
+ }
1164
+ ```
1165
+
1063
1166
  ---
1064
1167
 
1065
1168
  ### Themes.get()
@@ -1080,6 +1183,100 @@ Get a single theme by ID.
1080
1183
  response = LoopsSdk::Themes.get(theme_id: "clo5p8q0r0132ntx6flkunw89")
1081
1184
  ```
1082
1185
 
1186
+ #### Response
1187
+
1188
+ ```json
1189
+ {
1190
+ "id": "clo1z5q7s004yl70y3z4a5b6c",
1191
+ "name": "Default",
1192
+ "styles": { "backgroundColor": "#ffffff" },
1193
+ "isDefault": true,
1194
+ "createdAt": "2025-01-01T00:00:00.000Z",
1195
+ "updatedAt": "2025-01-01T00:00:00.000Z"
1196
+ }
1197
+ ```
1198
+
1199
+ ---
1200
+
1201
+ ### Themes.create()
1202
+
1203
+ Create a new email theme.
1204
+
1205
+ [API Reference](https://loops.so/docs/api-reference/create-theme)
1206
+
1207
+ #### Parameters
1208
+
1209
+ | Name | Type | Required | Notes |
1210
+ | -------- | ------ | -------- | -------------------------------------------------------------------------------------------------- |
1211
+ | `name` | string | Yes | |
1212
+ | `styles` | object | No | Style attributes matching LMX `<Style />` tag attribute names. See the API reference for the full list. |
1213
+
1214
+ #### Example
1215
+
1216
+ ```ruby
1217
+ response = LoopsSdk::Themes.create(
1218
+ name: "Dark mode",
1219
+ styles: { backgroundColor: "#111827", bodyColor: "#1f2937" }
1220
+ )
1221
+ ```
1222
+
1223
+ #### Response
1224
+
1225
+ ```json
1226
+ {
1227
+ "id": "clt3u5v7w9x1y3z5a7b9c1d3",
1228
+ "name": "Dark mode",
1229
+ "styles": {
1230
+ "backgroundColor": "#111827",
1231
+ "bodyColor": "#1f2937"
1232
+ },
1233
+ "isDefault": false,
1234
+ "createdAt": "2025-06-29T07:47:39.370Z",
1235
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1236
+ }
1237
+ ```
1238
+
1239
+ ---
1240
+
1241
+ ### Themes.update()
1242
+
1243
+ Update a theme's name and/or styles. Style changes cascade to emails using the theme.
1244
+
1245
+ [API Reference](https://loops.so/docs/api-reference/update-theme)
1246
+
1247
+ #### Parameters
1248
+
1249
+ | Name | Type | Required | Notes |
1250
+ | ---------- | ------ | -------- | ----- |
1251
+ | `theme_id` | string | Yes | |
1252
+ | `name` | string | No | |
1253
+ | `styles` | object | No | |
1254
+
1255
+ At least one of `name` or `styles` must be provided.
1256
+
1257
+ #### Example
1258
+
1259
+ ```ruby
1260
+ response = LoopsSdk::Themes.update(
1261
+ theme_id: "clo5p8q0r0132ntx6flkunw89",
1262
+ name: "Updated theme"
1263
+ )
1264
+ ```
1265
+
1266
+ #### Response
1267
+
1268
+ ```json
1269
+ {
1270
+ "id": "clt3u5v7w9x1y3z5a7b9c1d3",
1271
+ "name": "Updated theme",
1272
+ "styles": { "backgroundColor": "#111827" },
1273
+ "isDefault": false,
1274
+ "createdAt": "2025-06-29T07:47:39.370Z",
1275
+ "updatedAt": "2025-06-29T07:47:39.370Z",
1276
+ "affectedEmailCount": 3
1277
+ }
1278
+ ```
1279
+
1083
1280
  ---
1084
1281
 
1085
1282
  ### Components.list()
@@ -1101,6 +1298,28 @@ List email components.
1101
1298
  response = LoopsSdk::Components.list
1102
1299
  ```
1103
1300
 
1301
+ #### Response
1302
+
1303
+ ```json
1304
+ {
1305
+ "pagination": {
1306
+ "totalResults": 1,
1307
+ "returnedResults": 1,
1308
+ "perPage": 20,
1309
+ "totalPages": 1,
1310
+ "nextCursor": null,
1311
+ "nextPage": null
1312
+ },
1313
+ "data": [
1314
+ {
1315
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1316
+ "name": "Header",
1317
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>"
1318
+ }
1319
+ ]
1320
+ }
1321
+ ```
1322
+
1104
1323
  ---
1105
1324
 
1106
1325
  ### Components.get()
@@ -1121,6 +1340,88 @@ Get a single component by ID.
1121
1340
  response = LoopsSdk::Components.get(component_id: "clp6q9r1s0154ouy7gmlovx90")
1122
1341
  ```
1123
1342
 
1343
+ #### Response
1344
+
1345
+ ```json
1346
+ {
1347
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1348
+ "name": "Header",
1349
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>"
1350
+ }
1351
+ ```
1352
+
1353
+ ---
1354
+
1355
+ ### Components.create()
1356
+
1357
+ Create a new email component from an LMX body.
1358
+
1359
+ [API Reference](https://loops.so/docs/api-reference/create-component)
1360
+
1361
+ #### Parameters
1362
+
1363
+ | Name | Type | Required | Notes |
1364
+ | ------ | ------ | -------- | -------------------------------- |
1365
+ | `name` | string | Yes | |
1366
+ | `lmx` | string | Yes | The component body as LMX. |
1367
+
1368
+ #### Example
1369
+
1370
+ ```ruby
1371
+ response = LoopsSdk::Components.create(
1372
+ name: "Header",
1373
+ lmx: "<Paragraph>Welcome to Acme</Paragraph>"
1374
+ )
1375
+ ```
1376
+
1377
+ #### Response
1378
+
1379
+ ```json
1380
+ {
1381
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1382
+ "name": "Header",
1383
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>"
1384
+ }
1385
+ ```
1386
+
1387
+ ---
1388
+
1389
+ ### Components.update()
1390
+
1391
+ Update a component's name and/or LMX body. Body changes cascade to emails using the component.
1392
+
1393
+ [API Reference](https://loops.so/docs/api-reference/update-component)
1394
+
1395
+ #### Parameters
1396
+
1397
+ | Name | Type | Required | Notes |
1398
+ | -------------- | ------ | -------- | ----- |
1399
+ | `component_id` | string | Yes | |
1400
+ | `name` | string | No | |
1401
+ | `lmx` | string | No | |
1402
+
1403
+ At least one of `name` or `lmx` must be provided.
1404
+
1405
+ #### Example
1406
+
1407
+ ```ruby
1408
+ response = LoopsSdk::Components.update(
1409
+ component_id: "clp6q9r1s0154ouy7gmlovx90",
1410
+ name: "Updated Header"
1411
+ )
1412
+ ```
1413
+
1414
+ #### Response
1415
+
1416
+ ```json
1417
+ {
1418
+ "id": "clp2a6r8t005yl70d7e8f9g0h",
1419
+ "name": "Updated Header",
1420
+ "lmx": "<Paragraph>Welcome to Acme</Paragraph>",
1421
+ "affectedEmailCount": 2
1422
+ }
1423
+ ```
1424
+
1124
1425
  ---
1125
1426
 
1126
1427
  ### Campaigns.list()
@@ -1142,6 +1443,36 @@ List campaigns.
1142
1443
  response = LoopsSdk::Campaigns.list
1143
1444
  ```
1144
1445
 
1446
+ #### Response
1447
+
1448
+ ```json
1449
+ {
1450
+ "pagination": {
1451
+ "totalResults": 1,
1452
+ "returnedResults": 1,
1453
+ "perPage": 20,
1454
+ "totalPages": 1,
1455
+ "nextCursor": null,
1456
+ "nextPage": null
1457
+ },
1458
+ "data": [
1459
+ {
1460
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1461
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1462
+ "name": "Spring announcement",
1463
+ "status": "Draft",
1464
+ "createdAt": "2025-01-01T00:00:00.000Z",
1465
+ "updatedAt": "2025-01-01T00:00:00.000Z",
1466
+ "campaignGroupId": null,
1467
+ "mailingListId": null,
1468
+ "audienceSegmentId": null,
1469
+ "audienceFilter": null,
1470
+ "scheduling": { "method": "now", "timestamp": null }
1471
+ }
1472
+ ]
1473
+ }
1474
+ ```
1475
+
1145
1476
  ---
1146
1477
 
1147
1478
  ### Campaigns.create()
@@ -1173,6 +1504,25 @@ response = LoopsSdk::Campaigns.create(
1173
1504
  )
1174
1505
  ```
1175
1506
 
1507
+ #### Response
1508
+
1509
+ ```json
1510
+ {
1511
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1512
+ "name": "Spring announcement",
1513
+ "status": "Draft",
1514
+ "createdAt": "2025-01-01T00:00:00.000Z",
1515
+ "updatedAt": "2025-01-01T00:00:00.000Z",
1516
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1517
+ "emailMessageContentRevisionId": "clv8g2x4z012yl70n5o6p7q8r",
1518
+ "campaignGroupId": null,
1519
+ "mailingListId": null,
1520
+ "audienceSegmentId": null,
1521
+ "audienceFilter": null,
1522
+ "scheduling": { "method": "now", "timestamp": null }
1523
+ }
1524
+ ```
1525
+
1176
1526
  ---
1177
1527
 
1178
1528
  ### Campaigns.get()
@@ -1193,6 +1543,24 @@ Get a single campaign by ID.
1193
1543
  response = LoopsSdk::Campaigns.get(campaign_id: "cln4o7p9q0110msw5ekjtmv78")
1194
1544
  ```
1195
1545
 
1546
+ #### Response
1547
+
1548
+ ```json
1549
+ {
1550
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1551
+ "name": "Spring announcement",
1552
+ "status": "Draft",
1553
+ "createdAt": "2025-01-01T00:00:00.000Z",
1554
+ "updatedAt": "2025-01-01T00:00:00.000Z",
1555
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1556
+ "campaignGroupId": null,
1557
+ "mailingListId": null,
1558
+ "audienceSegmentId": null,
1559
+ "audienceFilter": null,
1560
+ "scheduling": { "method": "now", "timestamp": null }
1561
+ }
1562
+ ```
1563
+
1196
1564
  ---
1197
1565
 
1198
1566
  ### Campaigns.update()
@@ -1224,6 +1592,24 @@ response = LoopsSdk::Campaigns.update(
1224
1592
  )
1225
1593
  ```
1226
1594
 
1595
+ #### Response
1596
+
1597
+ ```json
1598
+ {
1599
+ "id": "cln0y4p6r003yl70i1j2k3l4m",
1600
+ "name": "Updated campaign name",
1601
+ "status": "Draft",
1602
+ "createdAt": "2025-01-01T00:00:00.000Z",
1603
+ "updatedAt": "2025-01-02T00:00:00.000Z",
1604
+ "emailMessageId": "clm9x3o5q002yl70a8b3c4d5e",
1605
+ "campaignGroupId": null,
1606
+ "mailingListId": null,
1607
+ "audienceSegmentId": null,
1608
+ "audienceFilter": null,
1609
+ "scheduling": { "method": "now", "timestamp": null }
1610
+ }
1611
+ ```
1612
+
1227
1613
  ---
1228
1614
 
1229
1615
  ### EmailMessages.get()
@@ -1244,6 +1630,24 @@ Get an email message, including its LMX content.
1244
1630
  response = LoopsSdk::EmailMessages.get(email_message_id: "cly8k3m0n0044jpx2bghepq45")
1245
1631
  ```
1246
1632
 
1633
+ #### Response
1634
+
1635
+ ```json
1636
+ {
1637
+ "id": "clm9x3o5q002yl70a8b3c4d5e",
1638
+ "campaignId": "cln0y4p6r003yl70i1j2k3l4m",
1639
+ "subject": "Hello",
1640
+ "previewText": "Preview text",
1641
+ "fromName": "Loops",
1642
+ "fromEmail": "hello",
1643
+ "replyToEmail": "",
1644
+ "emailFormat": "styled",
1645
+ "lmx": "<H1>...</H1><Paragraph>...</Paragraph>",
1646
+ "contentRevisionId": "clv8g2x4z012yl70n5o6p7q8r",
1647
+ "updatedAt": "2025-01-01T00:00:00.000Z"
1648
+ }
1649
+ ```
1650
+
1247
1651
  ---
1248
1652
 
1249
1653
  ### EmailMessages.update()
@@ -1282,10 +1686,28 @@ response = LoopsSdk::EmailMessages.update(
1282
1686
  preview_text: "See what's new",
1283
1687
  from_name: "Loops",
1284
1688
  from_email: "hello",
1285
- lmx: "<Email><Style /></Email>"
1689
+ lmx: "<Style /><Paragraph>...</Paragraph>"
1286
1690
  )
1287
1691
  ```
1288
1692
 
1693
+ #### Response
1694
+
1695
+ ```json
1696
+ {
1697
+ "id": "clm9x3o5q002yl70a8b3c4d5e",
1698
+ "campaignId": "cln0y4p6r003yl70i1j2k3l4m",
1699
+ "subject": "Spring announcement",
1700
+ "previewText": "See what's new",
1701
+ "fromName": "Loops",
1702
+ "fromEmail": "hello",
1703
+ "replyToEmail": "",
1704
+ "emailFormat": "styled",
1705
+ "lmx": "<Style /><Paragraph>...</Paragraph>",
1706
+ "contentRevisionId": "clv8g2x4z013yl70s9t0u1v2w",
1707
+ "updatedAt": "2025-01-02T00:00:00.000Z"
1708
+ }
1709
+ ```
1710
+
1289
1711
  ---
1290
1712
 
1291
1713
  ### EmailMessages.preview()
@@ -1314,46 +1736,126 @@ response = LoopsSdk::EmailMessages.preview(
1314
1736
  )
1315
1737
  ```
1316
1738
 
1739
+ #### Response
1740
+
1741
+ ```json
1742
+ {
1743
+ "id": "cle5f7g9h1i3j5k7l9m1n3p5"
1744
+ }
1745
+ ```
1746
+
1317
1747
  ---
1318
1748
 
1319
- ### CampaignGroups.list()
1749
+ ### EmailMessages.run_guardian()
1320
1750
 
1321
- List campaign groups.
1751
+ Run Guardian content validation on an email message and return errors and warnings.
1322
1752
 
1323
- [API Reference](https://loops.so/docs/api-reference/list-campaign-groups)
1753
+ [API Reference](https://loops.so/docs/api-reference/run-guardian-checks)
1324
1754
 
1325
1755
  #### Parameters
1326
1756
 
1327
- | Name | Type | Required | Notes |
1328
- | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
1329
- | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
1330
- | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
1757
+ | Name | Type | Required | Notes |
1758
+ | ------------------ | ------ | -------- | ----- |
1759
+ | `email_message_id` | string | Yes | |
1331
1760
 
1332
1761
  #### Example
1333
1762
 
1334
1763
  ```ruby
1335
- response = LoopsSdk::CampaignGroups.list
1764
+ response = LoopsSdk::EmailMessages.run_guardian(email_message_id: "cly8k3m0n0044jpx2bghepq45")
1765
+ ```
1766
+
1767
+ #### Response
1768
+
1769
+ ```json
1770
+ {
1771
+ "errors": [
1772
+ {
1773
+ "rule": "missingButtonHrefs",
1774
+ "title": "Missing button link",
1775
+ "description": "Buttons won't work without href value",
1776
+ "items": [{ "label": "Click here" }]
1777
+ }
1778
+ ],
1779
+ "warnings": []
1780
+ }
1336
1781
  ```
1337
1782
 
1338
1783
  ---
1339
1784
 
1340
- ### CampaignGroups.create()
1785
+ ### CampaignGroups.list()
1341
1786
 
1342
- Create a campaign group.
1787
+ List campaign groups.
1343
1788
 
1344
- [API Reference](https://loops.so/docs/api-reference/create-campaign-group)
1789
+ [API Reference](https://loops.so/docs/api-reference/list-campaign-groups)
1345
1790
 
1346
1791
  #### Parameters
1347
1792
 
1348
- | Name | Type | Required | Notes |
1349
- | ------------- | ------ | -------- | --------------------------------------- |
1350
- | `name` | string | Yes | Cannot be the reserved name "Unsorted". |
1351
- | `description` | string | No | An optional description for the group. |
1793
+ | Name | Type | Required | Notes |
1794
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
1795
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
1796
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
1352
1797
 
1353
1798
  #### Example
1354
1799
 
1355
1800
  ```ruby
1356
- response = LoopsSdk::CampaignGroups.create(name: "Newsletters", description: "Monthly updates")
1801
+ response = LoopsSdk::CampaignGroups.list
1802
+ ```
1803
+
1804
+ #### Response
1805
+
1806
+ ```json
1807
+ {
1808
+ "pagination": {
1809
+ "totalResults": 1,
1810
+ "returnedResults": 1,
1811
+ "perPage": 20,
1812
+ "totalPages": 1,
1813
+ "nextCursor": null,
1814
+ "nextPage": null
1815
+ },
1816
+ "data": [
1817
+ {
1818
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
1819
+ "name": "Newsletters",
1820
+ "description": "Monthly product updates",
1821
+ "createdAt": "2025-06-29T07:47:39.370Z",
1822
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1823
+ }
1824
+ ]
1825
+ }
1826
+ ```
1827
+
1828
+ ---
1829
+
1830
+ ### CampaignGroups.create()
1831
+
1832
+ Create a campaign group.
1833
+
1834
+ [API Reference](https://loops.so/docs/api-reference/create-campaign-group)
1835
+
1836
+ #### Parameters
1837
+
1838
+ | Name | Type | Required | Notes |
1839
+ | ------------- | ------ | -------- | --------------------------------------- |
1840
+ | `name` | string | Yes | Cannot be the reserved name "Unsorted". |
1841
+ | `description` | string | No | An optional description for the group. |
1842
+
1843
+ #### Example
1844
+
1845
+ ```ruby
1846
+ response = LoopsSdk::CampaignGroups.create(name: "Newsletters", description: "Monthly updates")
1847
+ ```
1848
+
1849
+ #### Response
1850
+
1851
+ ```json
1852
+ {
1853
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
1854
+ "name": "Newsletters",
1855
+ "description": "Monthly updates",
1856
+ "createdAt": "2025-06-29T07:47:39.370Z",
1857
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1858
+ }
1357
1859
  ```
1358
1860
 
1359
1861
  ---
@@ -1376,6 +1878,18 @@ Get a campaign group by ID.
1376
1878
  response = LoopsSdk::CampaignGroups.get(campaign_group_id: "clq7r0s2t0176pvz8hnmpwy01")
1377
1879
  ```
1378
1880
 
1881
+ #### Response
1882
+
1883
+ ```json
1884
+ {
1885
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
1886
+ "name": "Newsletters",
1887
+ "description": "Monthly product updates",
1888
+ "createdAt": "2025-06-29T07:47:39.370Z",
1889
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1890
+ }
1891
+ ```
1892
+
1379
1893
  ---
1380
1894
 
1381
1895
  ### CampaignGroups.update()
@@ -1403,6 +1917,18 @@ response = LoopsSdk::CampaignGroups.update(
1403
1917
  )
1404
1918
  ```
1405
1919
 
1920
+ #### Response
1921
+
1922
+ ```json
1923
+ {
1924
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
1925
+ "name": "Updated name",
1926
+ "description": "Monthly product updates",
1927
+ "createdAt": "2025-06-29T07:47:39.370Z",
1928
+ "updatedAt": "2025-06-29T07:47:39.370Z"
1929
+ }
1930
+ ```
1931
+
1406
1932
  ---
1407
1933
 
1408
1934
  ### AudienceSegments.list()
@@ -1424,6 +1950,36 @@ List audience segments.
1424
1950
  response = LoopsSdk::AudienceSegments.list
1425
1951
  ```
1426
1952
 
1953
+ #### Response
1954
+
1955
+ ```json
1956
+ {
1957
+ "pagination": {
1958
+ "totalResults": 1,
1959
+ "returnedResults": 1,
1960
+ "perPage": 20,
1961
+ "totalPages": 1,
1962
+ "nextCursor": null,
1963
+ "nextPage": null
1964
+ },
1965
+ "data": [
1966
+ {
1967
+ "id": "cls6e8g0i2k4m6o8q0s2u4w6",
1968
+ "name": "Power users",
1969
+ "description": "Contacts on the pro plan",
1970
+ "createdAt": "2025-06-29T07:47:39.370Z",
1971
+ "updatedAt": "2025-06-29T07:47:39.370Z",
1972
+ "filter": {
1973
+ "match": "all",
1974
+ "conditions": [
1975
+ { "type": "property", "key": "plan", "operator": "equals", "value": "pro" }
1976
+ ]
1977
+ }
1978
+ }
1979
+ ]
1980
+ }
1981
+ ```
1982
+
1427
1983
  ---
1428
1984
 
1429
1985
  ### AudienceSegments.get()
@@ -1444,6 +2000,72 @@ Get an audience segment by ID.
1444
2000
  response = LoopsSdk::AudienceSegments.get(audience_segment_id: "clr8s1t3u0198qw09iotqzx12")
1445
2001
  ```
1446
2002
 
2003
+ #### Response
2004
+
2005
+ ```json
2006
+ {
2007
+ "id": "cls6e8g0i2k4m6o8q0s2u4w6",
2008
+ "name": "Power users",
2009
+ "description": "Contacts on the pro plan",
2010
+ "createdAt": "2025-06-29T07:47:39.370Z",
2011
+ "updatedAt": "2025-06-29T07:47:39.370Z",
2012
+ "filter": {
2013
+ "match": "all",
2014
+ "conditions": [
2015
+ { "type": "property", "key": "plan", "operator": "equals", "value": "pro" }
2016
+ ]
2017
+ }
2018
+ }
2019
+ ```
2020
+
2021
+ ---
2022
+
2023
+ ### AudienceSegments.create()
2024
+
2025
+ Create a new audience segment.
2026
+
2027
+ [API Reference](https://loops.so/docs/api-reference/create-audience-segment)
2028
+
2029
+ #### Parameters
2030
+
2031
+ | Name | Type | Required | Notes |
2032
+ | ------------- | ------ | -------- | --------------------------------------------------------------------------- |
2033
+ | `name` | string | Yes | Must be unique within the team. |
2034
+ | `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). |
2035
+ | `description` | string | No | |
2036
+
2037
+ #### Example
2038
+
2039
+ ```ruby
2040
+ response = LoopsSdk::AudienceSegments.create(
2041
+ name: "Power users",
2042
+ filter: {
2043
+ match: "all",
2044
+ conditions: [
2045
+ { type: "property", key: "plan", operator: "equals", value: "pro" }
2046
+ ]
2047
+ }
2048
+ )
2049
+ ```
2050
+
2051
+ #### Response
2052
+
2053
+ ```json
2054
+ {
2055
+ "id": "cls6e8g0i2k4m6o8q0s2u4w6",
2056
+ "name": "Power users",
2057
+ "description": null,
2058
+ "createdAt": "2025-06-29T07:47:39.370Z",
2059
+ "updatedAt": "2025-06-29T07:47:39.370Z",
2060
+ "filter": {
2061
+ "match": "all",
2062
+ "conditions": [
2063
+ { "type": "property", "key": "plan", "operator": "equals", "value": "pro" }
2064
+ ]
2065
+ }
2066
+ }
2067
+ ```
2068
+
1447
2069
  ---
1448
2070
 
1449
2071
  ### Workflows.list()
@@ -1465,6 +2087,74 @@ List workflows.
1465
2087
  response = LoopsSdk::Workflows.list
1466
2088
  ```
1467
2089
 
2090
+ #### Response
2091
+
2092
+ ```json
2093
+ {
2094
+ "pagination": {
2095
+ "totalResults": 1,
2096
+ "returnedResults": 1,
2097
+ "perPage": 20,
2098
+ "totalPages": 1,
2099
+ "nextCursor": null,
2100
+ "nextPage": null
2101
+ },
2102
+ "data": [
2103
+ {
2104
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2105
+ "name": "Onboarding",
2106
+ "createdAt": "2025-06-29T07:47:39.370Z",
2107
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2108
+ }
2109
+ ]
2110
+ }
2111
+ ```
2112
+
2113
+ ---
2114
+
2115
+ ### Workflows.create()
2116
+
2117
+ Create a draft workflow with a blank trigger and exit node.
2118
+
2119
+ [API Reference](https://loops.so/docs/api-reference/create-workflow)
2120
+
2121
+ #### Parameters
2122
+
2123
+ | Name | Type | Required | Notes |
2124
+ | ----------------- | ------ | -------- | ---------------------------------------------- |
2125
+ | `name` | string | Yes | |
2126
+ | `description` | string | No | |
2127
+ | `mailing_list_id` | string | No | The mailing list the workflow sends to. |
2128
+
2129
+ #### Example
2130
+
2131
+ ```ruby
2132
+ response = LoopsSdk::Workflows.create(name: "Welcome series")
2133
+ ```
2134
+
2135
+ #### Response
2136
+
2137
+ ```json
2138
+ {
2139
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2140
+ "status": "Draft",
2141
+ "name": "Welcome series",
2142
+ "mailingListId": null,
2143
+ "rootNodeId": "cf16k73gq014h3mmj5b6jdi9r",
2144
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2145
+ "nodes": {
2146
+ "cf16k73gq014h3mmj5b6jdi9r": {
2147
+ "typeName": "BlankTrigger",
2148
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"]
2149
+ },
2150
+ "cf16k73gq014h3mmj5b4jdifg": {
2151
+ "typeName": "ExitAction",
2152
+ "nextNodeIds": []
2153
+ }
2154
+ }
2155
+ }
2156
+ ```
2157
+
1468
2158
  ---
1469
2159
 
1470
2160
  ### Workflows.get()
@@ -1485,6 +2175,122 @@ Get a simplified workflow graph.
1485
2175
  response = LoopsSdk::Workflows.get(workflow_id: "cls9t2u4v0210rx20jpuary23")
1486
2176
  ```
1487
2177
 
2178
+ #### Response
2179
+
2180
+ ```json
2181
+ {
2182
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2183
+ "status": "Draft",
2184
+ "name": "Onboarding",
2185
+ "mailingListId": null,
2186
+ "rootNodeId": "cf16k73gq014h3mmj5b6jdi9r",
2187
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2188
+ "nodes": {
2189
+ "cf16k73gq014h3mmj5b6jdi9r": {
2190
+ "typeName": "SignupTrigger",
2191
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"]
2192
+ },
2193
+ "cf16k73gq014h3mmj5b4jdifg": {
2194
+ "typeName": "ExitAction",
2195
+ "nextNodeIds": []
2196
+ }
2197
+ }
2198
+ }
2199
+ ```
2200
+
2201
+ ---
2202
+
2203
+ ### Workflows.update()
2204
+
2205
+ Update a workflow's display properties. To change the mailing list, use `Workflows.change_mailing_list()`.
2206
+
2207
+ [API Reference](https://loops.so/docs/api-reference/update-workflow)
2208
+
2209
+ #### Parameters
2210
+
2211
+ | Name | Type | Required | Notes |
2212
+ | ------------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------- |
2213
+ | `workflow_id` | string | Yes | |
2214
+ | `expected_revision_id` | string | Yes | The `workflowRevisionId` from the latest read or mutation. Pass `nil` for older workflows. |
2215
+ | `name` | string | No | |
2216
+ | `description` | string | No | |
2217
+
2218
+ At least one of `name` or `description` must be provided.
2219
+
2220
+ #### Example
2221
+
2222
+ ```ruby
2223
+ response = LoopsSdk::Workflows.update(
2224
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2225
+ expected_revision_id: "rev_123",
2226
+ name: "Updated name"
2227
+ )
2228
+ ```
2229
+
2230
+ #### Response
2231
+
2232
+ ```json
2233
+ {
2234
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2235
+ "status": "Draft",
2236
+ "name": "Updated name",
2237
+ "mailingListId": null,
2238
+ "rootNodeId": "cf16k73gq014h3mmj5b6jdi9r",
2239
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2240
+ "nodes": {
2241
+ "cf16k73gq014h3mmj5b6jdi9r": {
2242
+ "typeName": "BlankTrigger",
2243
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"]
2244
+ },
2245
+ "cf16k73gq014h3mmj5b4jdifg": {
2246
+ "typeName": "ExitAction",
2247
+ "nextNodeIds": []
2248
+ }
2249
+ }
2250
+ }
2251
+ ```
2252
+
2253
+ ---
2254
+
2255
+ ### Workflows.change_mailing_list()
2256
+
2257
+ 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.
2258
+
2259
+ [API Reference](https://loops.so/docs/api-reference/change-workflow-mailing-list)
2260
+
2261
+ #### Parameters
2262
+
2263
+ | Name | Type | Required | Notes |
2264
+ | ------------------------ | ------- | -------- | --------------------------------------------------------------------- |
2265
+ | `workflow_id` | string | Yes | |
2266
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2267
+ | `mailing_list_id` | string | Yes | Pass `nil` to clear the mailing list. |
2268
+ | `dry_run` | boolean | No | If `true`, validate without modifying. |
2269
+ | `queued_contact_policy` | string | No | `fail` (default) or `discard`. |
2270
+
2271
+ #### Example
2272
+
2273
+ ```ruby
2274
+ response = LoopsSdk::Workflows.change_mailing_list(
2275
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2276
+ expected_revision_id: "rev_123",
2277
+ mailing_list_id: "cm06f5v0e45nf0ml5754o9cix",
2278
+ dry_run: true
2279
+ )
2280
+ ```
2281
+
2282
+ #### Response
2283
+
2284
+ ```json
2285
+ {
2286
+ "status": "updated",
2287
+ "mailingListId": "cm06f5v0e45nf0ml5754o9cix",
2288
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2289
+ "queuedContactCount": 0,
2290
+ "queuedContactLimitReached": false
2291
+ }
2292
+ ```
2293
+
1488
2294
  ---
1489
2295
 
1490
2296
  ### Workflows.get_node()
@@ -1506,6 +2312,388 @@ Get detailed data for a single workflow node.
1506
2312
  response = LoopsSdk::Workflows.get_node(workflow_id: "cls9t2u4v0210rx20jpuary23", node_id: "clt0u3v5w0232sy31kqvbzs34")
1507
2313
  ```
1508
2314
 
2315
+ #### Response
2316
+
2317
+ ```json
2318
+ {
2319
+ "id": "cln8p0q2r4s6t8u0v2w4x6z8",
2320
+ "workflowId": "clw1a3b5c7d9e1f3g5h7i9j1",
2321
+ "typeName": "TimerAction",
2322
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"],
2323
+ "amount": 1,
2324
+ "unit": "h",
2325
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6"
2326
+ }
2327
+ ```
2328
+
2329
+ ---
2330
+
2331
+ ### Workflows.create_node()
2332
+
2333
+ Create a new default workflow node. Use `insert_mode: "between"` with `from_node_id`/`to_node_id`, or `insert_mode: "before"` with `before_node_id`.
2334
+
2335
+ [API Reference](https://loops.so/docs/api-reference/create-workflow-node)
2336
+
2337
+ #### Parameters
2338
+
2339
+ | Name | Type | Required | Notes |
2340
+ | ---------------------- | ------ | -------- | ---------------------------------------------------------------------------------------------- |
2341
+ | `workflow_id` | string | Yes | |
2342
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2343
+ | `insert_mode` | string | Yes | `between` or `before`. |
2344
+ | `node_type_name` | string | Yes | One of `AudienceFilter`, `BranchNode`, `ExperimentBranchNode`, `TimerAction`, `SendEmailAction`, `VariantNode`. |
2345
+ | `from_node_id` | string | No | Required when `insert_mode` is `between`. |
2346
+ | `to_node_id` | string | No | Required when `insert_mode` is `between`. |
2347
+ | `before_node_id` | string | No | Required when `insert_mode` is `before`. |
2348
+
2349
+ #### Example
2350
+
2351
+ ```ruby
2352
+ response = LoopsSdk::Workflows.create_node(
2353
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2354
+ expected_revision_id: "rev_123",
2355
+ insert_mode: "between",
2356
+ node_type_name: "TimerAction",
2357
+ from_node_id: "node_a",
2358
+ to_node_id: "node_b"
2359
+ )
2360
+ ```
2361
+
2362
+ #### Response
2363
+
2364
+ ```json
2365
+ {
2366
+ "node": {
2367
+ "id": "cln8p0q2r4s6t8u0v2w4x6z8",
2368
+ "typeName": "TimerAction",
2369
+ "nextNodeIds": ["node_b"],
2370
+ "amount": 0,
2371
+ "unit": "m",
2372
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6"
2373
+ },
2374
+ "workflow": {
2375
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2376
+ "status": "Draft",
2377
+ "name": "Welcome series",
2378
+ "mailingListId": null,
2379
+ "rootNodeId": "node_a",
2380
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2381
+ "nodes": {
2382
+ "node_a": {
2383
+ "typeName": "BlankTrigger",
2384
+ "nextNodeIds": ["cln8p0q2r4s6t8u0v2w4x6z8"]
2385
+ },
2386
+ "cln8p0q2r4s6t8u0v2w4x6z8": {
2387
+ "typeName": "TimerAction",
2388
+ "nextNodeIds": ["node_b"],
2389
+ "amount": 0,
2390
+ "unit": "m"
2391
+ },
2392
+ "node_b": {
2393
+ "typeName": "ExitAction",
2394
+ "nextNodeIds": []
2395
+ }
2396
+ }
2397
+ }
2398
+ }
2399
+ ```
2400
+
2401
+ ---
2402
+
2403
+ ### Workflows.update_node()
2404
+
2405
+ Update workflow-node-owned fields for a single node.
2406
+
2407
+ [API Reference](https://loops.so/docs/api-reference/update-workflow-node)
2408
+
2409
+ #### Parameters
2410
+
2411
+ | Name | Type | Required | Notes |
2412
+ | ---------------------- | ------ | -------- | ---------------------------------------------------------- |
2413
+ | `workflow_id` | string | Yes | |
2414
+ | `node_id` | string | Yes | |
2415
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2416
+ | `payload` | object | Yes | Node-type-specific fields. See the API reference. |
2417
+
2418
+ #### Example
2419
+
2420
+ ```ruby
2421
+ response = LoopsSdk::Workflows.update_node(
2422
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2423
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2424
+ expected_revision_id: "rev_123",
2425
+ payload: { amount: 2, unit: "d" }
2426
+ )
2427
+ ```
2428
+
2429
+ #### Response
2430
+
2431
+ ```json
2432
+ {
2433
+ "id": "cln8p0q2r4s6t8u0v2w4x6z8",
2434
+ "workflowId": "clw1a3b5c7d9e1f3g5h7i9j1",
2435
+ "typeName": "TimerAction",
2436
+ "nextNodeIds": ["cf16k73gq014h3mmj5b4jdifg"],
2437
+ "amount": 2,
2438
+ "unit": "d",
2439
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6"
2440
+ }
2441
+ ```
2442
+
2443
+ ---
2444
+
2445
+ ### Workflows.delete_node()
2446
+
2447
+ Delete a single workflow node. If contacts are queued, the API returns `"status": "queuedContactsFound"` — retry with `queued_contact_policy: "discard"` to delete.
2448
+
2449
+ [API Reference](https://loops.so/docs/api-reference/delete-workflow-node)
2450
+
2451
+ #### Parameters
2452
+
2453
+ | Name | Type | Required | Notes |
2454
+ | ----------------------- | ------- | -------- | ------------------------------ |
2455
+ | `workflow_id` | string | Yes | |
2456
+ | `node_id` | string | Yes | |
2457
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows.|
2458
+ | `dry_run` | boolean | No | |
2459
+ | `queued_contact_policy` | string | No | `fail` (default) or `discard`. |
2460
+
2461
+ #### Example
2462
+
2463
+ ```ruby
2464
+ response = LoopsSdk::Workflows.delete_node(
2465
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2466
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2467
+ expected_revision_id: "rev_123",
2468
+ dry_run: true
2469
+ )
2470
+ ```
2471
+
2472
+ #### Response
2473
+
2474
+ ```json
2475
+ {
2476
+ "status": "deleted",
2477
+ "nodeIds": ["clt0u3v5w0232sy31kqvbzs34"],
2478
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2479
+ "queuedContactCount": 0,
2480
+ "queuedContactLimitReached": false
2481
+ }
2482
+ ```
2483
+
2484
+ ---
2485
+
2486
+ ### Workflows.add_branch()
2487
+
2488
+ Add a branch and child node under an existing `BranchNode` or `ExperimentBranchNode`.
2489
+
2490
+ [API Reference](https://loops.so/docs/api-reference/add-workflow-branch)
2491
+
2492
+ #### Parameters
2493
+
2494
+ | Name | Type | Required | Notes |
2495
+ | ---------------------- | ------ | -------- | ------------------------------- |
2496
+ | `workflow_id` | string | Yes | |
2497
+ | `node_id` | string | Yes | The branch or experiment node. |
2498
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows. |
2499
+
2500
+ #### Example
2501
+
2502
+ ```ruby
2503
+ response = LoopsSdk::Workflows.add_branch(
2504
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2505
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2506
+ expected_revision_id: "rev_123"
2507
+ )
2508
+ ```
2509
+
2510
+ #### Response
2511
+
2512
+ ```json
2513
+ {
2514
+ "node": {
2515
+ "id": "cln0a2b4c6d8e0f2g4h6i8j0",
2516
+ "typeName": "AudienceFilter",
2517
+ "nextNodeIds": [],
2518
+ "appliesDownstream": false,
2519
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6"
2520
+ },
2521
+ "workflow": {
2522
+ "id": "clw1a3b5c7d9e1f3g5h7i9j1",
2523
+ "status": "Draft",
2524
+ "name": "Welcome series",
2525
+ "mailingListId": null,
2526
+ "rootNodeId": "clt0u3v5w0232sy31kqvbzs34",
2527
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2528
+ "nodes": {
2529
+ "clt0u3v5w0232sy31kqvbzs34": {
2530
+ "typeName": "BranchNode",
2531
+ "nextNodeIds": ["cln0a2b4c6d8e0f2g4h6i8j0"]
2532
+ },
2533
+ "cln0a2b4c6d8e0f2g4h6i8j0": {
2534
+ "typeName": "AudienceFilter",
2535
+ "nextNodeIds": []
2536
+ }
2537
+ }
2538
+ }
2539
+ }
2540
+ ```
2541
+
2542
+ ---
2543
+
2544
+ ### Workflows.delete_node_recursive()
2545
+
2546
+ Delete a node and its downstream subtree.
2547
+
2548
+ [API Reference](https://loops.so/docs/api-reference/delete-workflow-nodes)
2549
+
2550
+ #### Parameters
2551
+
2552
+ | Name | Type | Required | Notes |
2553
+ | ----------------------- | ------- | -------- | ------------------------------ |
2554
+ | `workflow_id` | string | Yes | |
2555
+ | `node_id` | string | Yes | Root of the subtree to delete. |
2556
+ | `expected_revision_id` | string | Yes | Pass `nil` for older workflows.|
2557
+ | `dry_run` | boolean | No | |
2558
+ | `queued_contact_policy` | string | No | `fail` (default) or `discard`. |
2559
+
2560
+ #### Example
2561
+
2562
+ ```ruby
2563
+ response = LoopsSdk::Workflows.delete_node_recursive(
2564
+ workflow_id: "cls9t2u4v0210rx20jpuary23",
2565
+ node_id: "clt0u3v5w0232sy31kqvbzs34",
2566
+ expected_revision_id: "rev_123",
2567
+ queued_contact_policy: "discard"
2568
+ )
2569
+ ```
2570
+
2571
+ #### Response
2572
+
2573
+ ```json
2574
+ {
2575
+ "status": "deleted",
2576
+ "nodeIds": [
2577
+ "clt0u3v5w0232sy31kqvbzs34",
2578
+ "cln9q1r3s5t7u9v1w3x5y7z9"
2579
+ ],
2580
+ "workflowRevisionId": "clrev0w0r1k2f3l4o5w6",
2581
+ "queuedContactCount": 0,
2582
+ "queuedContactLimitReached": false
2583
+ }
2584
+ ```
2585
+
2586
+ ---
2587
+
2588
+ ### EventPatterns.list()
2589
+
2590
+ List event patterns available to workflow event trigger nodes.
2591
+
2592
+ [API Reference](https://loops.so/docs/api-reference/list-event-patterns)
2593
+
2594
+ #### Parameters
2595
+
2596
+ | Name | Type | Required | Notes |
2597
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
2598
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
2599
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
2600
+
2601
+ #### Example
2602
+
2603
+ ```ruby
2604
+ response = LoopsSdk::EventPatterns.list
2605
+ ```
2606
+
2607
+ #### Response
2608
+
2609
+ ```json
2610
+ {
2611
+ "pagination": {
2612
+ "totalResults": 1,
2613
+ "returnedResults": 1,
2614
+ "perPage": 20,
2615
+ "totalPages": 1,
2616
+ "nextCursor": null,
2617
+ "nextPage": null
2618
+ },
2619
+ "data": [
2620
+ {
2621
+ "id": "cle1a2b3c004yl70d5e6f7g8h",
2622
+ "eventName": "signup",
2623
+ "incomingWebhookPlatform": null
2624
+ }
2625
+ ]
2626
+ }
2627
+ ```
2628
+
2629
+ ---
2630
+
2631
+ ### EventPatterns.get()
2632
+
2633
+ Get an event pattern by ID.
2634
+
2635
+ [API Reference](https://loops.so/docs/api-reference/get-event-pattern)
2636
+
2637
+ #### Parameters
2638
+
2639
+ | Name | Type | Required | Notes |
2640
+ | ------------------ | ------ | -------- | ----- |
2641
+ | `event_pattern_id` | string | Yes | |
2642
+
2643
+ #### Example
2644
+
2645
+ ```ruby
2646
+ response = LoopsSdk::EventPatterns.get(event_pattern_id: "cle1v2e3n4t5p6a7t8t9e0r1")
2647
+ ```
2648
+
2649
+ #### Response
2650
+
2651
+ ```json
2652
+ {
2653
+ "id": "cle1a2b3c004yl70d5e6f7g8h",
2654
+ "eventName": "signup",
2655
+ "eventProperties": [
2656
+ { "name": "plan", "type": "string" },
2657
+ { "name": "trialDays", "type": "number" }
2658
+ ],
2659
+ "incomingWebhookPlatform": null
2660
+ }
2661
+ ```
2662
+
2663
+ ---
2664
+
2665
+ ### EventPatterns.get_by_name()
2666
+
2667
+ Get an event pattern by event name. Event names are case-sensitive.
2668
+
2669
+ [API Reference](https://loops.so/docs/api-reference/get-event-pattern-by-name)
2670
+
2671
+ #### Parameters
2672
+
2673
+ | Name | Type | Required | Notes |
2674
+ | ------------ | ------ | -------- | ----- |
2675
+ | `event_name` | string | Yes | |
2676
+
2677
+ #### Example
2678
+
2679
+ ```ruby
2680
+ response = LoopsSdk::EventPatterns.get_by_name(event_name: "signup")
2681
+ ```
2682
+
2683
+ #### Response
2684
+
2685
+ ```json
2686
+ {
2687
+ "id": "cle1a2b3c004yl70d5e6f7g8h",
2688
+ "eventName": "signup",
2689
+ "eventProperties": [
2690
+ { "name": "plan", "type": "string" },
2691
+ { "name": "trialDays", "type": "number" }
2692
+ ],
2693
+ "incomingWebhookPlatform": null
2694
+ }
2695
+ ```
2696
+
1509
2697
  ---
1510
2698
 
1511
2699
  ### TransactionalGroups.list()
@@ -1527,6 +2715,30 @@ List transactional groups.
1527
2715
  response = LoopsSdk::TransactionalGroups.list
1528
2716
  ```
1529
2717
 
2718
+ #### Response
2719
+
2720
+ ```json
2721
+ {
2722
+ "pagination": {
2723
+ "totalResults": 1,
2724
+ "returnedResults": 1,
2725
+ "perPage": 20,
2726
+ "totalPages": 1,
2727
+ "nextCursor": null,
2728
+ "nextPage": null
2729
+ },
2730
+ "data": [
2731
+ {
2732
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2733
+ "name": "Onboarding",
2734
+ "description": "Top of funnel campaigns",
2735
+ "createdAt": "2025-06-29T07:47:39.370Z",
2736
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2737
+ }
2738
+ ]
2739
+ }
2740
+ ```
2741
+
1530
2742
  ---
1531
2743
 
1532
2744
  ### TransactionalGroups.create()
@@ -1548,6 +2760,18 @@ Create a transactional group.
1548
2760
  response = LoopsSdk::TransactionalGroups.create(name: "Account emails")
1549
2761
  ```
1550
2762
 
2763
+ #### Response
2764
+
2765
+ ```json
2766
+ {
2767
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2768
+ "name": "Account emails",
2769
+ "description": "",
2770
+ "createdAt": "2025-06-29T07:47:39.370Z",
2771
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2772
+ }
2773
+ ```
2774
+
1551
2775
  ---
1552
2776
 
1553
2777
  ### TransactionalGroups.get()
@@ -1568,6 +2792,18 @@ Get a transactional group by ID.
1568
2792
  response = LoopsSdk::TransactionalGroups.get(transactional_group_id: "clv2w3x4y0288xbb0kqrsuv67")
1569
2793
  ```
1570
2794
 
2795
+ #### Response
2796
+
2797
+ ```json
2798
+ {
2799
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2800
+ "name": "Account emails",
2801
+ "description": "",
2802
+ "createdAt": "2025-06-29T07:47:39.370Z",
2803
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2804
+ }
2805
+ ```
2806
+
1571
2807
  ---
1572
2808
 
1573
2809
  ### TransactionalGroups.update()
@@ -1595,6 +2831,18 @@ response = LoopsSdk::TransactionalGroups.update(
1595
2831
  )
1596
2832
  ```
1597
2833
 
2834
+ #### Response
2835
+
2836
+ ```json
2837
+ {
2838
+ "id": "clg7n5p3q1r9s7t5u3v1w9y7",
2839
+ "name": "Updated name",
2840
+ "description": "",
2841
+ "createdAt": "2025-06-29T07:47:39.370Z",
2842
+ "updatedAt": "2025-06-29T07:47:39.370Z"
2843
+ }
2844
+ ```
2845
+
1598
2846
  ---
1599
2847
 
1600
2848
  ### Uploads.upload()