loops_sdk 2.2.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
@@ -114,19 +114,56 @@ You can use custom contact properties in API calls. Please make sure to [add cus
114
114
  - [ContactProperties.list()](#contactpropertieslist)
115
115
  - [MailingLists.list()](#mailinglistslist)
116
116
  - [Events.send()](#eventssend)
117
- - [Transactional.send()](#transactionalsend)
118
117
  - [Transactional.list()](#transactionallist)
118
+ - [Transactional.create()](#transactionalcreate)
119
+ - [Transactional.get()](#transactionalget)
120
+ - [Transactional.update()](#transactionalupdate)
121
+ - [Transactional.ensure_draft()](#transactionalensure_draft)
122
+ - [Transactional.publish()](#transactionalpublish)
123
+ - [Transactional.send()](#transactionalsend)
119
124
  - [DedicatedSendingIps.list()](#dedicatedsendingipslist)
120
125
  - [Themes.list()](#themeslist)
121
126
  - [Themes.get()](#themesget)
127
+ - [Themes.create()](#themescreate)
128
+ - [Themes.update()](#themesupdate)
122
129
  - [Components.list()](#componentslist)
123
130
  - [Components.get()](#componentsget)
131
+ - [Components.create()](#componentscreate)
132
+ - [Components.update()](#componentsupdate)
124
133
  - [Campaigns.list()](#campaignslist)
125
134
  - [Campaigns.create()](#campaignscreate)
126
135
  - [Campaigns.get()](#campaignsget)
127
136
  - [Campaigns.update()](#campaignsupdate)
137
+ - [CampaignGroups.list()](#campaigngroupslist)
138
+ - [CampaignGroups.create()](#campaigngroupscreate)
139
+ - [CampaignGroups.get()](#campaigngroupsget)
140
+ - [CampaignGroups.update()](#campaigngroupsupdate)
141
+ - [AudienceSegments.list()](#audiencesegmentslist)
142
+ - [AudienceSegments.get()](#audiencesegmentsget)
143
+ - [AudienceSegments.create()](#audiencesegmentscreate)
144
+ - [Workflows.list()](#workflowslist)
145
+ - [Workflows.create()](#workflowscreate)
146
+ - [Workflows.get()](#workflowsget)
147
+ - [Workflows.update()](#workflowsupdate)
148
+ - [Workflows.change_mailing_list()](#workflowschange_mailing_list)
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)
128
158
  - [EmailMessages.get()](#emailmessagesget)
129
159
  - [EmailMessages.update()](#emailmessagesupdate)
160
+ - [EmailMessages.preview()](#emailmessagespreview)
161
+ - [EmailMessages.run_guardian()](#emailmessagesrun_guardian)
162
+ - [TransactionalGroups.list()](#transactionalgroupslist)
163
+ - [TransactionalGroups.create()](#transactionalgroupscreate)
164
+ - [TransactionalGroups.get()](#transactionalgroupsget)
165
+ - [TransactionalGroups.update()](#transactionalgroupsupdate)
166
+ - [Uploads.upload()](#uploadsupload)
130
167
 
131
168
  ---
132
169
 
@@ -206,7 +243,7 @@ This method will return a success or error message:
206
243
  ```json
207
244
  {
208
245
  "success": true,
209
- "id": "id_of_contact"
246
+ "id": "cll6b3i8901a9jx0oyktl2m4u"
210
247
  }
211
248
  ```
212
249
 
@@ -270,7 +307,7 @@ This method will return a success or error message:
270
307
  ```json
271
308
  {
272
309
  "success": true,
273
- "id": "id_of_contact"
310
+ "id": "cll6b3i8901a9jx0oyktl2m4u"
274
311
  }
275
312
  ```
276
313
 
@@ -726,6 +763,241 @@ This method will return a success or error:
726
763
 
727
764
  ---
728
765
 
766
+ ### Transactional.list()
767
+
768
+ List transactional emails, most recently created first.
769
+
770
+ [API Reference](https://loops.so/docs/api-reference/list-transactional-emails)
771
+
772
+ #### Parameters
773
+
774
+ | Name | Type | Required | Notes |
775
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
776
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
777
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
778
+
779
+ #### Example
780
+
781
+ ```ruby
782
+ response = LoopsSdk::Transactional.list
783
+
784
+ response = LoopsSdk::Transactional.list(perPage: 15, cursor: "cursor_value")
785
+ ```
786
+
787
+ #### Response
788
+
789
+ ```json
790
+ {
791
+ "pagination": {
792
+ "totalResults": 23,
793
+ "returnedResults": 20,
794
+ "perPage": 20,
795
+ "totalPages": 2,
796
+ "nextCursor": "clyo0q4wo01p59fsecyxqsh38",
797
+ "nextPage": "https://app.loops.so/api/v1/transactional-emails?cursor=clyo0q4wo01p59fsecyxqsh38&perPage=20"
798
+ },
799
+ "data": [
800
+ {
801
+ "id": "clfn0k1yg001imo0fdeqg30i8",
802
+ "name": "Welcome email",
803
+ "draftEmailMessageId": null,
804
+ "publishedEmailMessageId": "cly8k3m0n0044jpx2bghepq45",
805
+ "createdAt": "2023-11-06T17:48:07.249Z",
806
+ "updatedAt": "2023-11-06T17:48:07.249Z",
807
+ "dataVariables": ["confirmationUrl"]
808
+ }
809
+ ]
810
+ }
811
+ ```
812
+
813
+ ---
814
+
815
+ ### Transactional.create()
816
+
817
+ Create a transactional email. An empty draft email message is created automatically.
818
+
819
+ [API Reference](https://loops.so/docs/api-reference/create-transactional-email)
820
+
821
+ #### Parameters
822
+
823
+ | Name | Type | Required | Notes |
824
+ | ------------------------ | ------ | -------- | ------------------------------------------------------- |
825
+ | `name` | string | Yes | |
826
+ | `transactional_group_id` | string | No | The ID of the group to add this transactional email to. |
827
+
828
+ #### Example
829
+
830
+ ```ruby
831
+ response = LoopsSdk::Transactional.create(name: "Welcome email")
832
+ ```
833
+
834
+ #### Response
835
+
836
+ Returns the transactional email with `draftEmailMessageId` and `draftEmailMessageContentRevisionId`. Use these when updating the draft via `EmailMessages.update()`.
837
+
838
+ ```json
839
+ {
840
+ "id": "clfq6dinn000yl70fgwwyp82l",
841
+ "name": "Welcome email",
842
+ "draftEmailMessageId": "cly8k3m0n0044jpx2bghepq45",
843
+ "draftEmailMessageContentRevisionId": "clm9n4o6p0088lrz4dijslt67",
844
+ "publishedEmailMessageId": null,
845
+ "createdAt": "2023-11-06T17:48:07.249Z",
846
+ "updatedAt": "2023-11-06T17:48:07.249Z",
847
+ "dataVariables": []
848
+ }
849
+ ```
850
+
851
+ ---
852
+
853
+ ### Transactional.get()
854
+
855
+ Get a single transactional email by ID.
856
+
857
+ [API Reference](https://loops.so/docs/api-reference/get-transactional-email)
858
+
859
+ #### Parameters
860
+
861
+ | Name | Type | Required | Notes |
862
+ | ------------------ | ------ | -------- | ----- |
863
+ | `transactional_id` | string | Yes | |
864
+
865
+ #### Example
866
+
867
+ ```ruby
868
+ response = LoopsSdk::Transactional.get(transactional_id: "clfq6dinn000yl70fgwwyp82l")
869
+ ```
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
+
886
+ ---
887
+
888
+ ### Transactional.update()
889
+
890
+ Update a transactional email's name.
891
+
892
+ [API Reference](https://loops.so/docs/api-reference/update-transactional-email)
893
+
894
+ #### Parameters
895
+
896
+ | Name | Type | Required | Notes |
897
+ | ------------------------ | ------ | -------- | ------------------------------------------------------------ |
898
+ | `transactional_id` | string | Yes | |
899
+ | `name` | string | No | |
900
+ | `transactional_group_id` | string | No | The ID of the group to move this transactional email to. |
901
+
902
+ At least one field must be provided.
903
+
904
+ #### Example
905
+
906
+ ```ruby
907
+ response = LoopsSdk::Transactional.update(
908
+ transactional_id: "clfq6dinn000yl70fgwwyp82l",
909
+ name: "Updated name"
910
+ )
911
+ ```
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
+
928
+ ---
929
+
930
+ ### Transactional.ensure_draft()
931
+
932
+ Ensure a transactional email has a draft email message. If a draft already exists it is returned unchanged; otherwise a new empty draft is created.
933
+
934
+ [API Reference](https://loops.so/docs/api-reference/ensure-transactional-email-draft)
935
+
936
+ #### Parameters
937
+
938
+ | Name | Type | Required | Notes |
939
+ | ------------------ | ------ | -------- | ----- |
940
+ | `transactional_id` | string | Yes | |
941
+
942
+ #### Example
943
+
944
+ ```ruby
945
+ response = LoopsSdk::Transactional.ensure_draft(transactional_id: "clfq6dinn000yl70fgwwyp82l")
946
+ ```
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
+
964
+ ---
965
+
966
+ ### Transactional.publish()
967
+
968
+ Publish a transactional email's current draft. The draft becomes the published version and the draft is cleared.
969
+
970
+ [API Reference](https://loops.so/docs/api-reference/publish-transactional-email)
971
+
972
+ #### Parameters
973
+
974
+ | Name | Type | Required | Notes |
975
+ | ------------------ | ------ | -------- | ----- |
976
+ | `transactional_id` | string | Yes | |
977
+
978
+ #### Example
979
+
980
+ ```ruby
981
+ response = LoopsSdk::Transactional.publish(transactional_id: "clfq6dinn000yl70fgwwyp82l")
982
+ ```
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
+
999
+ ---
1000
+
729
1001
  ### Transactional.send()
730
1002
 
731
1003
  Send a transactional email to a contact. [Learn about sending transactional email](https://loops.so/docs/transactional/guide)
@@ -736,7 +1008,7 @@ Send a transactional email to a contact. [Learn about sending transactional emai
736
1008
 
737
1009
  | Name | Type | Required | Notes |
738
1010
  | ---------------------------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
739
- | `transactional_id` | string | Yes | The ID of the transactional email to send. |
1011
+ | `transactional_id` | string | Yes | The ID of the transactional email to send. |
740
1012
  | `email` | string | Yes | The email address of the recipient. |
741
1013
  | `add_to_audience` | boolean | No | If `true`, a contact will be created in your audience using the `email` value (if a matching contact doesn't already exist). |
742
1014
  | `data_variables` | object | No | An object containing data as defined by the data variables added to the transactional email template.<br />Values can be of type `string` or `number`. |
@@ -819,11 +1091,37 @@ If there is a problem with the request, a descriptive error message will be retu
819
1091
 
820
1092
  ---
821
1093
 
822
- ### Transactional.list()
1094
+ ### DedicatedSendingIps.list()
823
1095
 
824
- Get a list of published transactional emails.
1096
+ Get Loops' dedicated sending IP addresses.
825
1097
 
826
- [API Reference](https://loops.so/docs/api-reference/list-transactional-emails)
1098
+ [API Reference](https://loops.so/docs/api-reference/list-dedicated-sending-ips)
1099
+
1100
+ #### Parameters
1101
+
1102
+ None
1103
+
1104
+ #### Example
1105
+
1106
+ ```ruby
1107
+ response = LoopsSdk::DedicatedSendingIps.list
1108
+ ```
1109
+
1110
+ #### Response
1111
+
1112
+ Returns an array of IP address strings.
1113
+
1114
+ ```json
1115
+ ["1.2.3.4", "5.6.7.8"]
1116
+ ```
1117
+
1118
+ ---
1119
+
1120
+ ### Themes.list()
1121
+
1122
+ List email themes.
1123
+
1124
+ [API Reference](https://loops.so/docs/api-reference/list-themes)
827
1125
 
828
1126
  #### Parameters
829
1127
 
@@ -835,9 +1133,9 @@ Get a list of published transactional emails.
835
1133
  #### Example
836
1134
 
837
1135
  ```ruby
838
- response = LoopsSdk::Transactional.list
1136
+ response = LoopsSdk::Themes.list
839
1137
 
840
- response = LoopsSdk::Transactional.list(perPage: 15)
1138
+ response = LoopsSdk::Themes.list(perPage: 15, cursor: "cursor_value")
841
1139
  ```
842
1140
 
843
1141
  #### Response
@@ -845,107 +1143,138 @@ response = LoopsSdk::Transactional.list(perPage: 15)
845
1143
  ```json
846
1144
  {
847
1145
  "pagination": {
848
- "totalResults": 23,
849
- "returnedResults": 20,
1146
+ "totalResults": 1,
1147
+ "returnedResults": 1,
850
1148
  "perPage": 20,
851
- "totalPages": 2,
852
- "nextCursor": "clyo0q4wo01p59fsecyxqsh38",
853
- "nextPage": "https://app.loops.so/api/v1/transactional?cursor=clyo0q4wo01p59fsecyxqsh38&perPage=20"
1149
+ "totalPages": 1,
1150
+ "nextCursor": null,
1151
+ "nextPage": null
854
1152
  },
855
1153
  "data": [
856
1154
  {
857
- "id": "clfn0k1yg001imo0fdeqg30i8",
858
- "lastUpdated": "2023-11-06T17:48:07.249Z",
859
- "dataVariables": []
860
- },
861
- {
862
- "id": "cll42l54f20i1la0lfooe3z12",
863
- "lastUpdated": "2025-02-02T02:56:28.845Z",
864
- "dataVariables": [
865
- "confirmationUrl"
866
- ]
867
- },
868
- {
869
- "id": "clw6rbuwp01rmeiyndm80155l",
870
- "lastUpdated": "2024-05-14T19:02:52.000Z",
871
- "dataVariables": [
872
- "firstName",
873
- "lastName",
874
- "inviteLink"
875
- ]
876
- },
877
- ...
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
+ }
878
1162
  ]
879
1163
  }
880
1164
  ```
881
1165
 
882
1166
  ---
883
1167
 
884
- ### DedicatedSendingIps.list()
1168
+ ### Themes.get()
885
1169
 
886
- Get Loops' dedicated sending IP addresses.
1170
+ Get a single theme by ID.
887
1171
 
888
- [API Reference](https://loops.so/docs/api-reference/list-dedicated-sending-ips)
1172
+ [API Reference](https://loops.so/docs/api-reference/get-theme)
889
1173
 
890
1174
  #### Parameters
891
1175
 
892
- None
1176
+ | Name | Type | Required | Notes |
1177
+ | ---------- | ------ | -------- | ----- |
1178
+ | `theme_id` | string | Yes | |
893
1179
 
894
1180
  #### Example
895
1181
 
896
1182
  ```ruby
897
- response = LoopsSdk::DedicatedSendingIps.list
1183
+ response = LoopsSdk::Themes.get(theme_id: "clo5p8q0r0132ntx6flkunw89")
898
1184
  ```
899
1185
 
900
1186
  #### Response
901
1187
 
902
- Returns an array of IP address strings.
903
-
904
1188
  ```json
905
- ["1.2.3.4", "5.6.7.8"]
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
+ }
906
1197
  ```
907
1198
 
908
1199
  ---
909
1200
 
910
- ### Themes.list()
1201
+ ### Themes.create()
911
1202
 
912
- List email themes.
1203
+ Create a new email theme.
913
1204
 
914
- [API Reference](https://loops.so/docs/api-reference/list-themes)
1205
+ [API Reference](https://loops.so/docs/api-reference/create-theme)
915
1206
 
916
1207
  #### Parameters
917
1208
 
918
- | Name | Type | Required | Notes |
919
- | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
920
- | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
921
- | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
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. |
922
1213
 
923
1214
  #### Example
924
1215
 
925
1216
  ```ruby
926
- response = LoopsSdk::Themes.list
1217
+ response = LoopsSdk::Themes.create(
1218
+ name: "Dark mode",
1219
+ styles: { backgroundColor: "#111827", bodyColor: "#1f2937" }
1220
+ )
1221
+ ```
927
1222
 
928
- response = LoopsSdk::Themes.list(perPage: 15, cursor: "cursor_value")
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
+ }
929
1237
  ```
930
1238
 
931
1239
  ---
932
1240
 
933
- ### Themes.get()
1241
+ ### Themes.update()
934
1242
 
935
- Get a single theme by ID.
1243
+ Update a theme's name and/or styles. Style changes cascade to emails using the theme.
936
1244
 
937
- [API Reference](https://loops.so/docs/api-reference/get-theme)
1245
+ [API Reference](https://loops.so/docs/api-reference/update-theme)
938
1246
 
939
1247
  #### Parameters
940
1248
 
941
1249
  | Name | Type | Required | Notes |
942
1250
  | ---------- | ------ | -------- | ----- |
943
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.
944
1256
 
945
1257
  #### Example
946
1258
 
947
1259
  ```ruby
948
- response = LoopsSdk::Themes.get(theme_id: "theme_123")
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
+ }
949
1278
  ```
950
1279
 
951
1280
  ---
@@ -969,6 +1298,28 @@ List email components.
969
1298
  response = LoopsSdk::Components.list
970
1299
  ```
971
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
+
972
1323
  ---
973
1324
 
974
1325
  ### Components.get()
@@ -981,19 +1332,101 @@ Get a single component by ID.
981
1332
 
982
1333
  | Name | Type | Required | Notes |
983
1334
  | -------------- | ------ | -------- | ----- |
984
- | `component_id` | string | Yes | |
1335
+ | `component_id` | string | Yes | |
985
1336
 
986
1337
  #### Example
987
1338
 
988
1339
  ```ruby
989
- response = LoopsSdk::Components.get(component_id: "component_123")
1340
+ response = LoopsSdk::Components.get(component_id: "clp6q9r1s0154ouy7gmlovx90")
990
1341
  ```
991
1342
 
992
- ---
993
-
994
- ### Campaigns.list()
995
-
996
- List campaigns.
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
+
1425
+ ---
1426
+
1427
+ ### Campaigns.list()
1428
+
1429
+ List campaigns.
997
1430
 
998
1431
  [API Reference](https://loops.so/docs/api-reference/list-campaigns)
999
1432
 
@@ -1010,6 +1443,36 @@ List campaigns.
1010
1443
  response = LoopsSdk::Campaigns.list
1011
1444
  ```
1012
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
+
1013
1476
  ---
1014
1477
 
1015
1478
  ### Campaigns.create()
@@ -1020,14 +1483,44 @@ Create a draft campaign. An empty email message is created automatically.
1020
1483
 
1021
1484
  #### Parameters
1022
1485
 
1023
- | Name | Type | Required | Notes |
1024
- | ------ | ------ | -------- | ----- |
1025
- | `name` | string | Yes | |
1486
+ | Name | Type | Required | Notes |
1487
+ | ---------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------- |
1488
+ | `name` | string | Yes | |
1489
+ | `campaign_group_id` | string | No | The ID of the group to add this campaign to. |
1490
+ | `mailing_list_id` | string | No | The ID of the mailing list to send to. |
1491
+ | `audience_segment_id` | string | No | The ID of an audience segment. Setting this clears any `audience_filter`. |
1492
+ | `audience_filter` | object | No | A tree of audience conditions. See the API reference for the filter schema. |
1493
+ | `scheduling` | object | No | When the campaign should send. Use `{ method: "now" }` or `{ method: "schedule", timestamp: "..." }`. |
1026
1494
 
1027
1495
  #### Example
1028
1496
 
1029
1497
  ```ruby
1030
1498
  response = LoopsSdk::Campaigns.create(name: "Spring announcement")
1499
+
1500
+ response = LoopsSdk::Campaigns.create(
1501
+ name: "Spring announcement",
1502
+ mailing_list_id: "cm06f5v0e45nf0ml5754o9cix",
1503
+ scheduling: { method: "schedule", timestamp: "2026-06-01T10:00:00Z" }
1504
+ )
1505
+ ```
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
+ }
1031
1524
  ```
1032
1525
 
1033
1526
  ---
@@ -1042,38 +1535,81 @@ Get a single campaign by ID.
1042
1535
 
1043
1536
  | Name | Type | Required | Notes |
1044
1537
  | ------------- | ------ | -------- | ----- |
1045
- | `campaign_id` | string | Yes | |
1538
+ | `campaign_id` | string | Yes | |
1046
1539
 
1047
1540
  #### Example
1048
1541
 
1049
1542
  ```ruby
1050
- response = LoopsSdk::Campaigns.get(campaign_id: "campaign_123")
1543
+ response = LoopsSdk::Campaigns.get(campaign_id: "cln4o7p9q0110msw5ekjtmv78")
1544
+ ```
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
+ }
1051
1562
  ```
1052
1563
 
1053
1564
  ---
1054
1565
 
1055
1566
  ### Campaigns.update()
1056
1567
 
1057
- Update a draft campaign's name.
1568
+ Update a draft campaign's name, group, audience, or scheduling.
1058
1569
 
1059
1570
  [API Reference](https://loops.so/docs/api-reference/update-campaign)
1060
1571
 
1061
1572
  #### Parameters
1062
1573
 
1063
- | Name | Type | Required | Notes |
1064
- | ------------- | ------ | -------- | ----- |
1065
- | `campaign_id` | string | Yes | |
1066
- | `name` | string | Yes | |
1574
+ | Name | Type | Required | Notes |
1575
+ | --------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------- |
1576
+ | `campaign_id` | string | Yes | |
1577
+ | `name` | string | No | |
1578
+ | `campaign_group_id` | string | No | The ID of the group to move this campaign to. |
1579
+ | `mailing_list_id` | string | No | The ID of the mailing list to send to. |
1580
+ | `audience_segment_id` | string | No | The ID of an audience segment. Setting this clears any `audience_filter`. |
1581
+ | `audience_filter` | object | No | A tree of audience conditions. See the API reference for the filter schema. |
1582
+ | `scheduling` | object | No | When the campaign should send. Use `{ method: "now" }` or `{ method: "schedule", timestamp: "..." }`. |
1583
+
1584
+ At least one field must be provided.
1067
1585
 
1068
1586
  #### Example
1069
1587
 
1070
1588
  ```ruby
1071
1589
  response = LoopsSdk::Campaigns.update(
1072
- campaign_id: "campaign_123",
1590
+ campaign_id: "cln4o7p9q0110msw5ekjtmv78",
1073
1591
  name: "Updated campaign name"
1074
1592
  )
1075
1593
  ```
1076
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
+
1077
1613
  ---
1078
1614
 
1079
1615
  ### EmailMessages.get()
@@ -1086,12 +1622,30 @@ Get an email message, including its LMX content.
1086
1622
 
1087
1623
  | Name | Type | Required | Notes |
1088
1624
  | ------------------- | ------ | -------- | ----- |
1089
- | `email_message_id` | string | Yes | |
1625
+ | `email_message_id` | string | Yes | |
1090
1626
 
1091
1627
  #### Example
1092
1628
 
1093
1629
  ```ruby
1094
- response = LoopsSdk::EmailMessages.get(email_message_id: "message_123")
1630
+ response = LoopsSdk::EmailMessages.get(email_message_id: "cly8k3m0n0044jpx2bghepq45")
1631
+ ```
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
+ }
1095
1649
  ```
1096
1650
 
1097
1651
  ---
@@ -1104,31 +1658,1226 @@ Update an email message for a draft campaign.
1104
1658
 
1105
1659
  #### Parameters
1106
1660
 
1107
- | Name | Type | Required | Notes |
1108
- | ------------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------------- |
1109
- | `email_message_id` | string | Yes | |
1110
- | `expected_revision_id` | string | No | The `contentRevisionId` from your last fetch. Required to avoid stale concurrent updates. |
1111
- | `subject` | string | No | |
1112
- | `preview_text` | string | No | |
1113
- | `from_name` | string | No | |
1114
- | `from_email` | string | No | Sender username without `@` or domain. The team's sending domain is appended automatically. |
1115
- | `reply_to_email` | string | No | Must be empty or a valid email address. |
1116
- | `lmx` | string | No | Email body serialized as LMX. Styles must be embedded in the LMX `<Style />` tag. |
1661
+ | Name | Type | Required | Notes |
1662
+ | ------------------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------------- |
1663
+ | `email_message_id` | string | Yes | |
1664
+ | `expected_revision_id` | string | No | The `contentRevisionId` from your last fetch. Required to avoid stale concurrent updates. |
1665
+ | `subject` | string | No | |
1666
+ | `preview_text` | string | No | |
1667
+ | `from_name` | string | No | |
1668
+ | `from_email` | string | No | Sender username without `@` or domain. The team's sending domain is appended automatically. |
1669
+ | `reply_to_email` | string | No | Must be empty or a valid email address. |
1670
+ | `cc_email` | string | No | CC email address. Requires the team to have CC/BCC enabled. |
1671
+ | `bcc_email` | string | No | BCC email address. Requires the team to have CC/BCC enabled. |
1672
+ | `language_code` | string | No | Language code for the email. Requires translation to be enabled for the team. |
1673
+ | `email_format` | string | No | The rendering format of the email. One of `styled` or `plain`. |
1674
+ | `lmx` | string | No | Email body serialized as LMX. Styles must be embedded in the LMX `<Style />` tag. |
1675
+ | `contact_properties_fallbacks` | object | No | Fallback values for contact properties. A null value deletes the fallback. |
1676
+ | `event_properties_fallbacks` | object | No | Fallback values for event properties. A null value deletes the fallback. |
1677
+ | `data_variables_fallbacks` | object | No | Fallback values for data variables. A null value deletes the fallback. |
1117
1678
 
1118
1679
  #### Example
1119
1680
 
1120
1681
  ```ruby
1121
1682
  response = LoopsSdk::EmailMessages.update(
1122
- email_message_id: "message_123",
1123
- expected_revision_id: "revision_123",
1683
+ email_message_id: "cly8k3m0n0044jpx2bghepq45",
1684
+ expected_revision_id: "clm9n4o6p0088lrz4dijslt67",
1124
1685
  subject: "Spring announcement",
1125
1686
  preview_text: "See what's new",
1126
1687
  from_name: "Loops",
1127
1688
  from_email: "hello",
1128
- lmx: "<Email><Style /></Email>"
1689
+ lmx: "<Style /><Paragraph>...</Paragraph>"
1690
+ )
1691
+ ```
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
+
1711
+ ---
1712
+
1713
+ ### EmailMessages.preview()
1714
+
1715
+ Send a test preview of an email message to one or more addresses.
1716
+
1717
+ [API Reference](https://loops.so/docs/api-reference/preview-email-message)
1718
+
1719
+ #### Parameters
1720
+
1721
+ | Name | Type | Required | Notes |
1722
+ | -------------------- | -------- | -------- | --------------------------------------------------------------------------------- |
1723
+ | `email_message_id` | string | Yes | |
1724
+ | `emails` | string[] | Yes | One or more addresses to send the preview to. |
1725
+ | `contact_properties` | object | No | Contact property values to render. Accepted for campaign and workflow previews. |
1726
+ | `event_properties` | object | No | Event property values to render. Accepted for workflow previews only. |
1727
+ | `data_variables` | object | No | Transactional data variables to render. Accepted for transactional previews only. |
1728
+
1729
+ #### Example
1730
+
1731
+ ```ruby
1732
+ response = LoopsSdk::EmailMessages.preview(
1733
+ email_message_id: "cly8k3m0n0044jpx2bghepq45",
1734
+ emails: ["test@example.com"],
1735
+ contact_properties: { firstName: "Alex" }
1736
+ )
1737
+ ```
1738
+
1739
+ #### Response
1740
+
1741
+ ```json
1742
+ {
1743
+ "id": "cle5f7g9h1i3j5k7l9m1n3p5"
1744
+ }
1745
+ ```
1746
+
1747
+ ---
1748
+
1749
+ ### EmailMessages.run_guardian()
1750
+
1751
+ Run Guardian content validation on an email message and return errors and warnings.
1752
+
1753
+ [API Reference](https://loops.so/docs/api-reference/run-guardian-checks)
1754
+
1755
+ #### Parameters
1756
+
1757
+ | Name | Type | Required | Notes |
1758
+ | ------------------ | ------ | -------- | ----- |
1759
+ | `email_message_id` | string | Yes | |
1760
+
1761
+ #### Example
1762
+
1763
+ ```ruby
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
+ }
1781
+ ```
1782
+
1783
+ ---
1784
+
1785
+ ### CampaignGroups.list()
1786
+
1787
+ List campaign groups.
1788
+
1789
+ [API Reference](https://loops.so/docs/api-reference/list-campaign-groups)
1790
+
1791
+ #### Parameters
1792
+
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. |
1797
+
1798
+ #### Example
1799
+
1800
+ ```ruby
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
+ }
1859
+ ```
1860
+
1861
+ ---
1862
+
1863
+ ### CampaignGroups.get()
1864
+
1865
+ Get a campaign group by ID.
1866
+
1867
+ [API Reference](https://loops.so/docs/api-reference/get-campaign-group)
1868
+
1869
+ #### Parameters
1870
+
1871
+ | Name | Type | Required | Notes |
1872
+ | ---- | ------ | -------- | ----- |
1873
+ | `campaign_group_id` | string | Yes | |
1874
+
1875
+ #### Example
1876
+
1877
+ ```ruby
1878
+ response = LoopsSdk::CampaignGroups.get(campaign_group_id: "clq7r0s2t0176pvz8hnmpwy01")
1879
+ ```
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
+
1893
+ ---
1894
+
1895
+ ### CampaignGroups.update()
1896
+
1897
+ Update a campaign group's name or description.
1898
+
1899
+ [API Reference](https://loops.so/docs/api-reference/update-campaign-group)
1900
+
1901
+ #### Parameters
1902
+
1903
+ | Name | Type | Required | Notes |
1904
+ | ------------- | ------ | -------- | --------------------------------------- |
1905
+ | `campaign_group_id` | string | Yes | |
1906
+ | `name` | string | No | Cannot be the reserved name "Unsorted". |
1907
+ | `description` | string | No | |
1908
+
1909
+ At least one field must be provided.
1910
+
1911
+ #### Example
1912
+
1913
+ ```ruby
1914
+ response = LoopsSdk::CampaignGroups.update(
1915
+ campaign_group_id: "clq7r0s2t0176pvz8hnmpwy01",
1916
+ name: "Updated name"
1129
1917
  )
1130
1918
  ```
1131
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
+
1932
+ ---
1933
+
1934
+ ### AudienceSegments.list()
1935
+
1936
+ List audience segments.
1937
+
1938
+ [API Reference](https://loops.so/docs/api-reference/list-audience-segments)
1939
+
1940
+ #### Parameters
1941
+
1942
+ | Name | Type | Required | Notes |
1943
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
1944
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
1945
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
1946
+
1947
+ #### Example
1948
+
1949
+ ```ruby
1950
+ response = LoopsSdk::AudienceSegments.list
1951
+ ```
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
+
1983
+ ---
1984
+
1985
+ ### AudienceSegments.get()
1986
+
1987
+ Get an audience segment by ID.
1988
+
1989
+ [API Reference](https://loops.so/docs/api-reference/get-audience-segment)
1990
+
1991
+ #### Parameters
1992
+
1993
+ | Name | Type | Required | Notes |
1994
+ | ---- | ------ | -------- | ----- |
1995
+ | `audience_segment_id` | string | Yes | |
1996
+
1997
+ #### Example
1998
+
1999
+ ```ruby
2000
+ response = LoopsSdk::AudienceSegments.get(audience_segment_id: "clr8s1t3u0198qw09iotqzx12")
2001
+ ```
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
+
2069
+ ---
2070
+
2071
+ ### Workflows.list()
2072
+
2073
+ List workflows.
2074
+
2075
+ [API Reference](https://loops.so/docs/api-reference/list-workflows)
2076
+
2077
+ #### Parameters
2078
+
2079
+ | Name | Type | Required | Notes |
2080
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
2081
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
2082
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
2083
+
2084
+ #### Example
2085
+
2086
+ ```ruby
2087
+ response = LoopsSdk::Workflows.list
2088
+ ```
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
+
2158
+ ---
2159
+
2160
+ ### Workflows.get()
2161
+
2162
+ Get a simplified workflow graph.
2163
+
2164
+ [API Reference](https://loops.so/docs/api-reference/get-workflow)
2165
+
2166
+ #### Parameters
2167
+
2168
+ | Name | Type | Required | Notes |
2169
+ | ---- | ------ | -------- | ----- |
2170
+ | `workflow_id` | string | Yes | |
2171
+
2172
+ #### Example
2173
+
2174
+ ```ruby
2175
+ response = LoopsSdk::Workflows.get(workflow_id: "cls9t2u4v0210rx20jpuary23")
2176
+ ```
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
+
2294
+ ---
2295
+
2296
+ ### Workflows.get_node()
2297
+
2298
+ Get detailed data for a single workflow node.
2299
+
2300
+ [API Reference](https://loops.so/docs/api-reference/get-workflow-node)
2301
+
2302
+ #### Parameters
2303
+
2304
+ | Name | Type | Required | Notes |
2305
+ | ------------ | ------ | -------- | ----- |
2306
+ | `workflow_id`| string | Yes | |
2307
+ | `node_id` | string | Yes | |
2308
+
2309
+ #### Example
2310
+
2311
+ ```ruby
2312
+ response = LoopsSdk::Workflows.get_node(workflow_id: "cls9t2u4v0210rx20jpuary23", node_id: "clt0u3v5w0232sy31kqvbzs34")
2313
+ ```
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
+
2697
+ ---
2698
+
2699
+ ### TransactionalGroups.list()
2700
+
2701
+ List transactional groups.
2702
+
2703
+ [API Reference](https://loops.so/docs/api-reference/list-transactional-groups)
2704
+
2705
+ #### Parameters
2706
+
2707
+ | Name | Type | Required | Notes |
2708
+ | --------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------- |
2709
+ | `perPage` | integer | No | How many results to return per page. Must be between 10 and 50. Defaults to 20 if omitted. |
2710
+ | `cursor` | string | No | A cursor, to return a specific page of results. Cursors can be found from the `pagination.nextCursor` value in each response. |
2711
+
2712
+ #### Example
2713
+
2714
+ ```ruby
2715
+ response = LoopsSdk::TransactionalGroups.list
2716
+ ```
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
+
2742
+ ---
2743
+
2744
+ ### TransactionalGroups.create()
2745
+
2746
+ Create a transactional group.
2747
+
2748
+ [API Reference](https://loops.so/docs/api-reference/create-transactional-group)
2749
+
2750
+ #### Parameters
2751
+
2752
+ | Name | Type | Required | Notes |
2753
+ | ------------- | ------ | -------- | --------------------------------------- |
2754
+ | `name` | string | Yes | Cannot be the reserved name "Unsorted". |
2755
+ | `description` | string | No | An optional description for the group. |
2756
+
2757
+ #### Example
2758
+
2759
+ ```ruby
2760
+ response = LoopsSdk::TransactionalGroups.create(name: "Account emails")
2761
+ ```
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
+
2775
+ ---
2776
+
2777
+ ### TransactionalGroups.get()
2778
+
2779
+ Get a transactional group by ID.
2780
+
2781
+ [API Reference](https://loops.so/docs/api-reference/get-transactional-group)
2782
+
2783
+ #### Parameters
2784
+
2785
+ | Name | Type | Required | Notes |
2786
+ | ---- | ------ | -------- | ----- |
2787
+ | `transactional_group_id` | string | Yes | |
2788
+
2789
+ #### Example
2790
+
2791
+ ```ruby
2792
+ response = LoopsSdk::TransactionalGroups.get(transactional_group_id: "clv2w3x4y0288xbb0kqrsuv67")
2793
+ ```
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
+
2807
+ ---
2808
+
2809
+ ### TransactionalGroups.update()
2810
+
2811
+ Update a transactional group's name or description.
2812
+
2813
+ [API Reference](https://loops.so/docs/api-reference/update-transactional-group)
2814
+
2815
+ #### Parameters
2816
+
2817
+ | Name | Type | Required | Notes |
2818
+ | ------------- | ------ | -------- | --------------------------------------- |
2819
+ | `transactional_group_id` | string | Yes | |
2820
+ | `name` | string | No | Cannot be the reserved name "Unsorted". |
2821
+ | `description` | string | No | |
2822
+
2823
+ At least one field must be provided.
2824
+
2825
+ #### Example
2826
+
2827
+ ```ruby
2828
+ response = LoopsSdk::TransactionalGroups.update(
2829
+ transactional_group_id: "clv2w3x4y0288xbb0kqrsuv67",
2830
+ name: "Updated name"
2831
+ )
2832
+ ```
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
+
2846
+ ---
2847
+
2848
+ ### Uploads.upload()
2849
+
2850
+ Upload an image file for use in LMX email content.
2851
+
2852
+ Supported image types: JPEG, PNG, GIF, and WebP (max 4 MB). MIME type is detected from file contents, or pass `content_type:` to override.
2853
+
2854
+ [API Reference](https://loops.so/docs/api-reference/create-upload)
2855
+
2856
+ #### Parameters
2857
+
2858
+ | Name | Type | Required | Notes |
2859
+ | --------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
2860
+ | `path` | string | Yes | Path to the image file on disk. |
2861
+ | `content_type` | string | No | MIME type override. Supported: `image/jpeg`, `image/png`, `image/gif`, `image/webp`. |
2862
+
2863
+ #### Example
2864
+
2865
+ ```ruby
2866
+ response = LoopsSdk::Uploads.upload(path: "./header.png")
2867
+
2868
+ # Use the returned URL in LMX
2869
+ lmx = %(<Image src="#{response['finalUrl']}" alt="Header" />)
2870
+ ```
2871
+
2872
+ #### Response
2873
+
2874
+ ```json
2875
+ {
2876
+ "emailAssetId": "clu1v4w6x0254tz42lrcwat45",
2877
+ "finalUrl": "https://cdn.loops.so/clu1v4w6x0254tz42lrcwat45.png"
2878
+ }
2879
+ ```
2880
+
1132
2881
  ---
1133
2882
 
1134
2883
  ## Testing
@@ -1139,4 +2888,4 @@ Run tests with `bundle exec rspec`.
1139
2888
 
1140
2889
  ## Contributing
1141
2890
 
1142
- Bug reports and pull requests are welcome. Please read our [Contributing Guidelines](CONTRIBUTING.md).
2891
+ Bug reports and pull requests are welcome. Please read our [Contributing Guidelines](CONTRIBUTING.md).