-
Notifications
You must be signed in to change notification settings - Fork 0
/
attentive.json
16764 lines (16764 loc) · 513 KB
/
attentive.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{
"openapi": "3.0.3",
"info": {
"description": "For any questions, reach out to your Attentive point of contact (if applicable) or [api@attentivemobile.com](mailto:api@attentivemobile.com).\n\n# Authentication\n\n<!-- ReDoc-Inject: <security-definitions> -->",
"title": "Attentive API",
"version": ""
},
"servers": [
{
"url": "https://api.attentivemobile.com/v1",
"description": "Attentive API"
}
],
"security": [
{
"OAuthFlow": []
}
],
"tags": [
{
"name": "Test Authentication",
"description": "Use the Test Authentication endpoint to test your unique token that you received from Attentive. Make sure to save your token because all API requests are authenticated using bearer tokens. The response should include information specific to your company.\n"
},
{
"name": "Access Token",
"description": "Public applications must authenticate using the OAuth 2.0 specification to use Attentive’s API resources. Attentive uses OAuth 2.0’s authorization code grant flow to issue access tokens on behalf of users. When an application is installed on our platform, an authorization code is generated automatically. This authorization code must be exchanged for an access token to authenticate grant and control permissions for your application.\n"
},
{
"name": "Webhooks",
"description": "Create and manage webhooks"
},
{
"name": "eCommerce",
"description": "Use the eCommerce API to trigger an event when a user views a product, adds a product to their shopping cart, or makes a purchase.",
"x-beta": false
},
{
"name": "Custom Events",
"description": "Use the Custom Events API to send user actions to use in the Attentive Segment Builder and Journey Builder. This data cannot contain any sensitive or special categories of information as defined in applicable data protection and privacy laws, including the California Consumer Privacy Act (CCPA) and California Privacy Rights Act (CPRA). See a list of specific categories of data you cannot share with Attentive [here](https://docs.attentivemobile.com/pages/legal-docs/pi-disclaimer/).",
"x-beta": false
},
{
"name": "Custom Attributes",
"description": "Use the Custom Attributes API to apply customizable data or characteristics to each of your subscribers. You can then build segments based on that information to send targeted campaigns and journeys.",
"x-beta": false
},
{
"name": "Subscribers",
"description": "Use the Subscribers API to manage subscriptions. With this API, you can programmatically subscribe and unsubscribe users from subscriptions.\n",
"x-beta": false
},
{
"name": "Product Catalog",
"description": "Our product catalog API unlocks the ability to send high-performing journeys such as back in stock, low inventory, and price drop. It also lets you segment your customers and branch journeys using product data.\n- **Create high performing journeys**, such as back in stock, low inventory, and price drop.\n- **Segment customers** based on their past browsing, add to cart, and purchasing activity using product data such as name, category, tag, price, and other attributes.\n- **Branch journeys** based on product attributes or inventory, such as only sending a message if the product is in stock.\n\nAttentive also has several integrations with popular e-commerce platforms that sync product data to Attentive. These are available in the Integrations tab.\n\n## How to Get Started\n1. [Create an Attentive app to get an api key](https://docs.attentivemobile.com/pages/create-and-manage-custom-apps/)\n2. [Review the authentication workflow](https://docs.attentivemobile.com/pages/authentication/)\n3. Read through the product catalog file format you'll need to generate to send us your product catalog.\n4. Either use the sample script below to send us the file(s) you've generated, or implement something similar.\n5. By default, `validateOnly` will be set to `true` when initiating the upload, in order for you to develop without \nsaving the catalog Attentive side. Once you're ready for production, go ahead and set `validateOnly` to `false`.\n6. Once the file has been uploaded, contact your CSM to confirm that the data quality is high enough for the Attentive product data features to be enabled.\n\n### Sample CLI Utility Script\nFeel free to reuse and adapt this Python3 script to send Attentive the catalog files you've generated.\n```python\nimport argparse\nimport requests # you may need to install this https://docs.python-requests.org/en/latest/user/install/\nimport json\nimport time\n\nfrom distutils.util import strtobool\n\n\nAPI_KEY = '' # Set this\nAPI_BASE_URL = 'https://api.attentivemobile.com'\nSTATUS_INTERVAL = 10\n\n\ndef initiate_catalog_upload(validate_only, api_key):\n post_url = API_BASE_URL + '/v1/product-catalog/uploads'\n r = requests.post(\n post_url,\n json={'validateOnly': validate_only},\n headers={'Authorization': 'Bearer ' + api_key},\n )\n assert r.status_code == 200, \"Are you sure your api key is correct?\"\n resp = r.json()\n return resp['uploadId'], resp['uploadUrl']\n\n\ndef print_errors(errors):\n print(\"Validation Errors:\")\n for error in errors:\n print(json.dumps(error))\n\n\ndef wait_for_validation(upload_id, validate_only, api_key, counter):\n \"\"\"\n The file at this point should now be queued up and we are awaiting validation. If there are any\n validation errors, we'll print them out from here. You may want to integrate your own more\n advanced monitoring.\n \"\"\"\n time.sleep(STATUS_INTERVAL)\n get_url = API_BASE_URL + '/v1/product-catalog/uploads/' + upload_id\n r = requests.get(get_url, headers={'Authorization': 'Bearer ' + api_key}).json()\n if r['errors']:\n # Consider implementing alerting over here\n print_errors(r['errors'])\n if r['status'] == 'validated':\n return\n # waiting approximately an hour before giving up. Totally up to you how long, but Attentive should\n # rarely be behind an hour behind in processing\n if counter == 360:\n print(\"Giving up on waiting for validation for \" + upload_id)\n return\n\n wait_for_validation(upload_id, validate_only, api_key, counter + 1)\n\n\ndef upload_catalog(filepath, validate_only, api_key):\n upload_id, upload_url = initiate_catalog_upload(validate_only, api_key)\n with open(filepath, 'rb') as f:\n r = requests.put(upload_url, data=f)\n assert r.status_code == 200, 'Unexpected issue uploading'\n wait_for_validation(upload_id, validate_only, api_key, 0)\n return upload_id\n\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(\n description='CLI utility script to demonstrate and assist in sending your generated product catalog to Attentive via its API'\n )\n parser.add_argument('filepath', help='Path to your catalog file')\n parser.add_argument(\n '--validateOnly',\n default=True,\n dest='validate_only',\n type=lambda x: bool(strtobool(x)),\n help='Boolean flag to choose whether or not Attentive should only validate the file for correctness only or not. '\n 'Defaults to True to prevent saving invalid data during development. Set to False when everything passes without errors.',\n )\n parser.add_argument(\n '--apiKey',\n dest='api_key',\n help='You can pass the API Key in here as an argument or set it in the API_KEY variable at the top of this file',\n )\n args = parser.parse_args()\n\n key = args.api_key or API_KEY\n assert key, (\n 'Please either pass in the apiKey argument to this script, or '\n 'update the API_KEY variable at the top of this file to authenticate to Attentive'\n )\n id = upload_catalog(args.filepath, args.validate_only, key)\n```\n## Product catalog file format\nYou can use the Product Catalog API to provide Attentive with your entire product catalog\nprogrammatically in order to segment or branch on different product attributes to send more\ntargeted SMS messages. It also unlocks other unique product features (e.g. Back In-Stock Journeys).\n\nIn order to use the Product Catalog API, you must first provide Attentive with your full or\npartial product catalog as a ndjson format file to be HTTP file uploaded to a specified URL. Each\nline in the file represents a full product, as described in the sample below. This article\noutlines the structure of each product/row, as well as the definitions of each object and field.\n\n```\nTop-level product\n{\n \"name\": string, *\n \"id\": string, *\n \"description\": string,\n \"brand\": string,\n \"link\": string, *\n \"lastUpdated\": timestamp, *\n \"categories\": Array<string>,\n \"tags\": Array<string>,\n \"productOptions\": Array<ProductOption>,\n \"images\": Array<Image>,\n \"attributes\": Array<Attribute>\n \"variants\": Array<Variant> *,\n \"collections\": Array<String>\n}\n\nProduct Option\n{\n \"name\": string, *\n \"position\": int, *\n \"values\": Array<string> *\n}\n\nImage\n{\n \"position\": int,\n \"alt\": string,\n \"src\": string, *\n \"width\": int,\n \"height\": int,\n \"variantIds\": Array<string>\n}\n\nAttribute\n{\n \"name\": string, *\n \"value\": string *\n}\n\nVariant\n{\n \"name\": string, * full name\n \"id\": string, *\n \"position\": int,\n \"prices\": Array<Price>,\n \"availableForPurchase\": boolean, *\n \"inventoryQuantity\": int,\n \"productOptionValues\": Array<ProductOptionValue>,\n \"link\": string, *\n \"lastUpdated\": timestamp, *\n \"attributes\": Array<Attribute>\n}\n\nProduct Option Value\n{\n \"productOptionName\": string, *\n \"value\": string *\n}\n\nPrice\n{\n \"currencyCode\": string, *\n \"amount\": string, *\n \"activeTime\": timestamp,\n \"expirationTime\": timestamp,\n \"compareAtPrice\": string,\n}\n```\n\n### Definition of terms\n\n#### Product\nProducts are the goods that you are selling on your website. For example, it can be a t-shirt\nor a pair of shoes. This is the root JSON object on each line and is inherently required.\n\n| Field | Description | Type | Required |\n| ----- | ----- | -------- | ---- |\n| id | This is your product ID and the field we key off of. It must be unique across your catalog. You need this unique ID to make any updates to the product by uploading your product with the same id. The maximum length of the ID is 256 characters. | string | Required |\n| name | The name of your product. Note that this is how it appears in messages. The maximum length of the name is 256 characters. | string | Required |\n| description | A short description of your product. The maximum length of the description is 1024 characters. | string | Optional |\n| brand | Brand for your product. Maximum length of brand is 256 characters. | string | Optional |\n| link | The link to your product's detail page online. Maximum length of link is 2048 characters. (http(s) required) | string | Required |\n| lastUpdated | The date and time (in UTC) of when your product was last updated. | [timestamp](https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC) | Required |\n| categories | One or more categories in your taxonomy associated with this product. You can specify up to ten categories per product and each category can be up to 64 characters long. | Array<string> | Optional |\n| tags | One or more tags that are associated with and used to categorize this product. You can specify up to 50 tags per product and each tag can be up to 64 characters long. | Array<string> | Optional |\n| productOptions | See [Product Option](#product-option). Up to 10 ProductOptions are allowed per product and up to 100 values are allowed per option. | Array<ProductOption> | Optional |\n| images | See [Image](#image). Up to 250 images are allowed per product. | Array<Image> | Optional |\n| attributes | See [Attribute](#attribute). Up to 100 attributes are allowed per product. | Array<Attribute> | Optional |\n| variants | See [Variant](#variant). Up to 100 variants are allowed per product. | Array<Variant> | Required |\n| collections | The grouping of products that this product belongs to. Up to 20 collections per product are allowed. | Array<string> | Optional |\n\n\n#### Variant\nA variant can be added to a Product to represent one version of a product with several options.\nThe Product has a variant for every possible combination of its options. Following the example\nin [Product](#product), a variant is a size small and color black. An order can begin fulfillment once a\nvariant is selected. Each product must have at least one variant\n\n| Field | Description | Type | Required |\n| ----- | ----- | -------- | ---- |\n| id | This is your variant ID and the field we key off of. It must be unique across your catalog. You need this unique ID to make any updates to the product by uploading your variant with the same id. The maximum length of the ID is 256 characters. | string | Required |\n| name | The name of this variant. Note that this is how it appears in messages to your subscribers. The maximum length of the name is 256 characters. | string | Required |\n| position | The order in which this variant appears among all the other variants that belong to this product. The variant with the lowest number is the default variant. This value must be greater than or equal to 0. | int | Optional |\n| link | The link to your variant's detail page online. If there is no link for your variant, you can use your product link. The maximum length of the link is 2048 characters. (http(s) required) | string | Required |\n| prices | See [Price](#price) | Array<Price> | Required\n| availableForPurchase | Is this variant still being sold? | boolean | Required |\n| inventoryQuantity | The amount of the variant available before the variant is out of stock. | int | Optional |\n| productOptionValues | The combination of options that this variant represents for the product. See [Product Options](#product-option) section for details. | Array<ProductOption> | Optional |\n| attributes | See [Attribute](#attribute). Up to 100 Variants allowed. | Array<Attribute> | Optional |\n| lastUpdated | The date and time (in UTC) of when your product or variant was last updated. | [timestamp](https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC) | Required |\n\n\n#### Product Option\nProduct options are the dimensions or choices that a customer has to select to add a variant\nto their cart. Following our previous [Product](#product) example above with the t-shirt, the\ncustomer needs to select the size and color they want. In this example, size and color are the\nproduct options.\n\n| Field | Description | Type | Required |\n| ----- | ----- | -------- | ---- |\n| name | The name/title of this product option. Up to 256 characters long | string | Required |\n| position | The order in which this option appears among all the other options. The option with the lowest number is first in the order. This value must be greater than or equal to 0. | int | Required |\n| values | The different possible values for this product option. Up to 256 characters long for each value. | Array<string> | Required |\n\n\n#### Product Option Value\nProduct option values are the unique product option selections associated with a given variant.\nThese are contextualized within the Variant object.\n\n| Field | Description | Type | Required |\n| ----- | ----- | -------- | ---- |\n| productOptionName | The product option name | string | Required |\n| value | The selection or value for this variant | string | Required |\n\n\n#### Attribute\nGeneric key/value data for products/variants that can be used for categorizing.\n\n| Field | Description | Type | Required |\n| ----- | ----- | -------- | ---- |\n| name | The attribute name. Up to 64 characters long. | string | Required |\n| value | The attribute value. Up to 64 characters long. | string | Required |\n\n\n#### Image\nData for the images associated with your products and variants that can be used for Attentive product\nmessaging and experiences.\n\n| Field | Description | Type | Required |\n| ----- | ----- | -------- | ---- |\n| src | The URL to the image (http(s) required). | string | Required |\n| alt | The alt text for the image. This is used as a back up in case an image can't be displayed and standard on the web. Up to 512 characters allowed. | string | Optional |\n| width | The width of the image in pixels | int | Optional |\n| height | The height of the image in pixels | int | Optional |\n| variantIds | The list of variant IDs this image applies to. Each id must match the ID of the field of one of the variants. | Array<string> | Optional |\n| position | The order in which images are considered for a product or variant. The image with the lowest position will be the default image. In other words, ascending order. Defaults to 0. | int | Optional |\n\n\n#### Price\nA price associated with the variant. You may have more than one price and currency associated\nwith a variant. In those cases, Attentive will likely choose the lowest available price in\nmessaging experiences.\n\n| Field | Description | Type | Required |\n| ----- | ----- | -------- | ---- |\n| currencyCode | This follows the three letter currency codes (e.g. USD). For more info, see [ISO-4217](https://www.iso.org/iso-4217-currency-codes.html). | string | Required |\n| amount | The price amount | string | Required |\n| activeTime | The date and time of when the price is scheduled. This is useful if you are scheduling a price for the future, such as an upcoming sale. | [timestamp](https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC) | Required |\n| expirationTime | The date and time of when the price is expiring. This is commonly used for when a sale ends. Both activeTime and expirationTime can be used independently. Note that this must be set in the future. | [timestamp](https://en.wikipedia.org/wiki/ISO_8601#Time_offsets_from_UTC) | Optional |\n| compareAtPrice | This is another price field, and use of this field implies the variant is on sale. This is the price buyers compare against to evaluate how good a sale is. Example: \"The price was <compareAtPrice> but now is <amount>! Get it while it lasts\" | string | Optional |\n\n\n### Formatted example of one product\n```\n{\n \"name\": \"Nasa T-Shirt\",\n \"id\": \"PD-123\",\n \"description\": \"A very popular T-Shirt\",\n \"brand\": \"NASA\",\n \"link\": \"https://www.google.com\",\n \"lastUpdated\": \"2021-10-05T18:08:28+00:00\",\n \"categories\": [\"Shirts\"],\n \"tags\": [\"Summer Sale\", \"Space\"],\n \"productOptions\": [\n {\"name\": \"Color\", \"position\": 0, \"values\": [\"Blue\", \"Black\"]},\n {\"name\": \"Size\", \"position\": 1, \"values\": [\"Small\", \"Medium\", \"Large\"]},\n ],\n \"images\": [\n {\"src\": \"https://www.google.com\", \"alt\": \"Picture of Nasa T-Shirt in Blue\", \"position\": 0, \"height\": 250, \"width\": 400, \"variantIds\": [\"VD-234\"]},\n {\"src\": \"https://www.google.com\", \"alt\": \"Another Picture of Nasa T-Shirt in Black\", \"position\": 0, \"height\": 250, \"width\": 400, \"variantIds\": [\"VD-235\", \"VD-236\"]}\n ],\n \"attributes\": [{\"name\": \"Fabric\", \"value\": \"Cotton\"}],\n \"variants\": [\n {\n \"name\": \"Nasa T-Shirt - Blue - Small\",\n \"id\": \"VD-234\",\n \"position\": 0,\n \"prices\": [\n {\"currencyCode\": \"USD\", \"amount\": \"10.00\"}\n ],\n \"availableForPurchase\": true,\n \"productOptionValues\": [\n {\"productOptionName\": \"Color\", \"value\": \"Blue\"},\n {\"productOptionName\": \"Size\", \"value\": \"Small\"},\n ],\n \"link\": \"https://www.google.com\",\n \"lastUpdated\": \"2021-10-05T18:08:28+00:00\",\n },\n {\n \"name\": \"Nasa T-Shirt - Black - Medium\",\n \"id\": \"VD-235\",\n \"position\": 1,\n \"prices\": [\n {\"currencyCode\": \"USD\", \"amount\": \"10.00\"}\n ],\n \"availableForPurchase\": true,\n \"productOptionValues\": [\n {\"productOptionName\": \"Color\", \"value\": \"Black\"},\n {\"productOptionName\": \"Size\", \"value\": \"Medium\"},\n ],\n \"link\": \"https://www.google.com\",\n \"lastUpdated\": \"2021-10-05T18:08:28+00:00\",\n },\n {\n \"name\": \"Nasa T-Shirt - Black - Large\",\n \"id\": \"VD-236\",\n \"position\": 2,\n \"prices\": [\n {\"currencyCode\": \"USD\", \"amount\": \"10.00\"}\n ],\n \"availableForPurchase\": true,\n \"productOptionValues\": [\n {\"productOptionName\": \"Color\", \"value\": \"Black\"},\n {\"productOptionName\": \"Size\", \"value\": \"Large\"},\n ],\n \"link\": \"https://www.google.com\",\n \"lastUpdated\": \"2021-10-05T18:08:28+00:00\",\n }\n ]\n}\n ```\n### Development Workflow Tips\n- As you're developing your code to generate this catalog file for Attentive, it's helpful to\nvalidate your generated files without any side effects on Attentive. To do that, please set\nthe `validateOnly` boolean to `true` when calling `/product-catalog/uploads`. Please note\nyour file is not immediately processed once the upload compeletes, but you can check the\nstatus of your upload with the same endpoint.\n- The cadence of uploading your catalog to Attentive is up to you. A daily job works great for most\nof our users, but we'd prefer we limit it to no more than every few hours if you're sending us\nyour entire catalog on every upload. However, if you would like to send us \"delta uploads\"\n(only products/variants which have changed since your last upload), please feel free to be more\nliberal with your cadence. If we find there are upload frequency issues, we'll be sure to reach\nout.\n- If you have any questions as to how to map your catalog to the Attentive format above,\nplease reach out to your client strategy partner at Attentive.\n\n### File Upload Limits\n- All files need to be UTF-8 encoded.\n- 2GB maximum file size\n- 4mb maximum line/product size\n- 500k line/product limit per file",
"x-beta": false
},
{
"name": "Privacy Request",
"description": "You can use the Privacy Request API in order to comply with [California Consumer Privacy Act](https://epic.org/california-consumer-privacy-act-ccpa/) deletion requests through Attentive. For more information, you can review [Attentive’s FAQs for CCPA](https://attentivemobile.atlassian.net/wiki/download/attachments/629309474/Attentive%20FAQs%20for%20CCPA.pdf?version=4&modificationDate=1585845052142&cacheVersion=1&api=v2) or the [Important Notice Regarding the CCPA of 2018](https://attentivemobile.atlassian.net/wiki/download/attachments/629309474/Important%20Notice%20Regarding%20the%20California%20Consumer%20Privacy%20Act%20of%202018%20(CCPA).pdf?version=1&modificationDate=1579115568586&cacheVersion=1&api=v2).\n",
"x-beta": true
},
{
"name": "Identity",
"description": "Use the Identity API to manage user identifiers. With this API, you can programmatically add a client user identifier or custom identifier(s) to a user.\nYou should only use clientUserId and customIdentifiers to send Attentive your system-assigned unique identifiers (even when testing).\n\nNotes:\n - Sending duplicate values could lead to unintentionally bridging users together.\n - Don't use customIdentifiers to send attributes (e.g., first name, last name). To send those types of attributes,\n use our [Custom Attributes API](https://docs.attentivemobile.com/openapi/reference/tag/Custom-Attributes/).\n - Avoid sending null values or empty strings. If you don't have a valid identifier, you should omit the field\n from the payload.\n",
"x-beta": true
}
],
"paths": {
"/me": {
"x-external": true,
"get": {
"x-external": true,
"x-emits-event": true,
"summary": "Me",
"description": "Make a call to this endpoint to test your unique token that you generate in the Attentive product.",
"operationId": "getMe",
"tags": [
"Test Authentication"
],
"responses": {
"200": {
"description": "Get information about the authenticated user",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetMeResponseDto"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/webhooks": {
"x-external": true,
"get": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"webhooks:write"
]
}
],
"summary": "List webhooks",
"description": "Make an API call to this endpoint to list existing webhooks.",
"operationId": "getWebhooks",
"tags": [
"Webhooks"
],
"responses": {
"200": {
"description": "existing webhooks.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetWebhooksResponseDto"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
},
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"webhooks:write"
]
}
],
"summary": "Create webhook",
"description": "Make an API call to this endpoint to subscribe to a webhook.\n\nEvents are a collection of strings of the following types:\n* `sms.subscribed`\n* `sms.sent`\n* `sms.message_link_click`\n* `email.subscribed`\n* `email.unsubscribed`\n* `email.message_link_click`\n* `email.opened`\n* `custom_attribute.set`\n\nEvent types are case sensitive.\n\nAll events included will be sent to the URL.\n",
"operationId": "createWebhook",
"tags": [
"Webhooks"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateWebhookRequestDto"
}
}
}
},
"responses": {
"201": {
"description": "Webhook has been created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateWebhookResponseDto"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/webhooks/{webhookId}": {
"delete": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"webhooks:write"
]
}
],
"summary": "Delete webhook",
"description": "Make an API call to this endpoint to remove a webhook.",
"operationId": "deleteWebhook",
"parameters": [
{
"name": "webhookId",
"in": "path",
"description": "id of the webhook to delete",
"required": true,
"schema": {
"type": "string"
}
}
],
"tags": [
"Webhooks"
],
"responses": {
"204": {
"description": "Webhook has been deleted"
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
},
"put": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"webhooks:write"
]
}
],
"summary": "Update webhook",
"description": "Make an API call to this endpoint to update a webhook",
"operationId": "updateWebhook",
"parameters": [
{
"name": "webhookId",
"in": "path",
"description": "The id of the webhook to update",
"required": true,
"schema": {
"type": "string"
}
}
],
"tags": [
"Webhooks"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateWebhookRequestDto"
}
}
}
},
"responses": {
"200": {
"description": "Webhook has been updated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateWebhookResponseDto"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/authorization-codes/tokens": {
"x-external": true,
"x-requires-auth": false,
"x-sensitive": true,
"post": {
"x-external": true,
"summary": "Access Token",
"description": "Make a call to this endpoint to exchange a temporary authorization code for an access token.",
"operationId": "createTokenViaAuthorizationCode",
"tags": [
"Access Token"
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"type": "object",
"properties": {
"grant_type": {
"type": "string",
"description": "Type of grant. Currently, authorization_code is the only accepted type.",
"example": "authorization_code"
},
"code": {
"type": "string",
"description": "Authorization code provided after the user authorizes the scopes during the application install process.",
"example": "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3"
},
"redirect_uri": {
"type": "string",
"description": "The same redirect URI that was used when requesting the authorization code.",
"example": "https://test.com"
},
"client_id": {
"type": "string",
"description": "The application’s client ID which can be found on the Manage Distribution tab for the application.",
"example": "9f7a2f11a4f849f59268869ec766111c"
},
"client_secret": {
"type": "string",
"description": "The application’s client secret which can be found on the Manage Distribution tab for the application.",
"example": "0FvaRpPi5KBC4Izj9ALA0AG8J2WdcBhU"
}
}
}
}
}
},
"responses": {
"200": {
"description": "Token generated",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateTokenResponseDto"
}
}
}
}
}
}
},
"/events/ecommerce/product-view": {
"x-external": true,
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"ecommerce:write"
]
}
],
"summary": "Product view",
"description": "Make a call to this endpoint when a user views a product.",
"operationId": "postProductViewEvents",
"tags": [
"eCommerce"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProductViewRequest"
}
}
}
},
"responses": {
"200": {
"description": "Ok"
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/events/ecommerce/add-to-cart": {
"x-external": true,
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"ecommerce:write"
]
}
],
"summary": "Add to cart",
"description": "Make a call to this endpoint when a user adds a product to their shopping cart.",
"operationId": "postAddToCartEvents",
"tags": [
"eCommerce"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AddToCartRequest"
}
}
}
},
"responses": {
"200": {
"description": "Ok"
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/events/ecommerce/purchase": {
"x-external": true,
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"ecommerce:write"
]
}
],
"summary": "Purchase",
"description": "Make a call to this endpoint when a user generates an order or purchase.",
"operationId": "postPurchaseEvents",
"tags": [
"eCommerce"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PurchaseRequest"
}
}
}
},
"responses": {
"200": {
"description": "Ok"
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/events/custom": {
"x-external": true,
"description": "Custom Events\n",
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"events:write"
]
}
],
"summary": "Custom Events",
"description": "Make a call to this endpoint for any event-based data representing user actions.",
"operationId": "postCustomEvents",
"tags": [
"Custom Events"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomEventsRequest"
}
}
}
},
"responses": {
"200": {
"description": "Ok"
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/attributes/custom": {
"x-external": true,
"description": "Custom Attributes\n",
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"attributes:write"
]
}
],
"summary": "Custom Attributes",
"description": "Make a call to this endpoint for any attribute-based data. There are no limits to the amount of custom attributes that can be created. Note that you can create net-new properties with this API, however, it cannot be used to create new values for an existing UI-created property name. If a property name is created through the Attentive platform, all possible property values must also be defined in the platform.",
"operationId": "postCustomAttributes",
"tags": [
"Custom Attributes"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CustomAttributesRequest"
}
}
}
},
"responses": {
"200": {
"description": "Ok"
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/subscriptions": {
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"subscriptions:write"
]
}
],
"summary": "Subscribe user",
"description": "Make a call to this endpoint to opt-in a user to a subscription. \n\nNotes:\n- A legal disclosure is required when a user is opted-in programmatically.\n\n - For marketing messages, required [legal language](https://docs.attentivemobile.com/pages/legal-docs/legal-disclosure-language/) must be included.\n\n - For transactional messages, you must include a [transactional opt-in unit](https://docs.attentivemobile.com/pages/legal-docs/legal-transactional/).\n\n- By default, if a subscription already exists, it will try and record the attempt to create the subscription again. For TEXT subscriptions, this will result in a message being sent to the person indicating that they are already subscribed.\n",
"operationId": "addSubscriptions",
"tags": [
"Subscribers"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AddSubscriptionsRequestDto"
}
}
}
},
"responses": {
"202": {
"description": "Accepted a create subscription(s) request. The response body will contain info about which subscription(s) already exist, and which subscription(s) will be created (asynchronously).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/AddSubscriptionsResponseDto"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
},
"get": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"subscriptions:write"
]
}
],
"summary": "Get subscription eligibility for a user",
"description": "Make a call to this endpoint to list all subscription types and channels a user is subscribed to. You can query for a subscriber using either their phone number or email. One of the query parameters is required in order to look up a subscriber. As an example, you can use this endpoint to check if a subscriber is eligible to receive SMS or email campaigns, and then send them a message based on that eligibility.\n",
"operationId": "getSubscriptions",
"tags": [
"Subscribers"
],
"parameters": [
{
"in": "query",
"name": "phone",
"schema": {
"type": "string",
"example": "+13115552368"
},
"description": "A user's phone number we use to fetch subscription eligibility."
},
{
"in": "query",
"name": "email",
"schema": {
"type": "string",
"example": "test@gmail.com"
},
"description": "A user's email we use to fetch subscription eligibility."
}
],
"responses": {
"200": {
"description": "Successfully accepted get subscriptions request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetSubscriptionsResponseDto"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/subscriptions/unsubscribe": {
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"subscriptions:write"
]
}
],
"summary": "Unsubscribe subscriptions for a user",
"description": "Make a call to this endpoint to unsubscribe a user from a subscription type or channel. If no subscriptions\nare present in the request, the user is unsubscribed from all subscriptions. If subscriptions are present\nin the request, the user is unsubscribed from the requested type or channel combination. By default, if a\nsubscription exists, but the user is already unsubscribed, it records the attempt to unsubscribe the\nsubscription again. For TEXT subscriptions, a message is sent to the person indicating that they are\nunsubscribed.\n\nFor the user object, the email data point determines which email subscriptions a user has and the phone data point \ndetermines which text (or sms) subscriptions a user has. Passing in an email does not locate, nor unsubscribe, \na user from any sms subscriptions. Similarly, passing in a phone does not locate, nor unsubscribe, a user from any email subscriptions.\n",
"operationId": "unsubscribeSubscriptions",
"tags": [
"Subscribers"
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnsubscribeSubscriptionsRequestDto"
}
}
}
},
"responses": {
"202": {
"description": "Accepted an unsubscribe subscription(s) request. The response body will contain info about which subscription(s) are already unsubscribed, and which subscription(s) will be unsubscribed (asynchronously).",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UnsubscribeSubscriptionsResponseDto"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/product-catalog/uploads": {
"x-external": true,
"description": "Upload Product Catalog\n",
"post": {
"x-external": true,
"x-emits-event": true,
"security": [
{
"OAuthFlow": [
"product_catalogs:write"
]
}
],
"summary": "Upload Product Catalog",
"description": "Make a call to this endpoint to start sending Attentive your full or partial product catalog.\nThe process starts with a POST to this endpoint, where you will receive a pre-signed AWS S3 URL. You can\nuse any language's http request libraries for uploading a file via HTTP. Here's how to do it with `curl` as an example\n\n\n```\ncurl --upload-file ${fileNameLocally} ${presignedURL}\n```\n\n\nand here's an example in Python\n```python\nimport requests\nwith open(filepath, 'rb') as f:\n r = requests.put(upload_url, data=f)\n```\n\n[Here are examples from AWS](https://docs.aws.amazon.com/AmazonS3/latest/userguide/PresignedUrlUploadObject.html) on how to send the file over in popular programming languages. Note that you aren't interested in\nthe portion of these examples where they are generating the pre-signed URL, but simply the http call to upload the file to the URL.\n\nOnce your full or partial product catalog begins to upload, the status is updated to\n`validating` while it's processing and the file is checked for errors. After the upload is\nvalidated, the status is updated to `validated` or skips directly to `completed`. When the\ncatalog is saved, the status is updated to `SAVED`. In cases where there are errors saving\nthe data, Attentive Engineering is notified and will contact you.\n\n\nTo ensure there are no validation errors in the file, you can set `validateOnly` parameter\nto `true` to avoid saving any data. We highly recommend this during your development to get a\nfaster feedback loop on any validation errors as you generate files.\n\n\nIf there are no errors returned in the upload response, your product catalog uploaded\nsuccessfully.\n",
"operationId": "postUpload",
"tags": [
"Product Catalog"
],
"requestBody": {
"required": false,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CatalogUploadRequest"
}
}
}
},
"responses": {
"200": {
"description": "Ok",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CatalogUploadResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
},
"get": {
"x-external": true,
"security": [
{
"OAuthFlow": [
"product_catalogs:read"
]
}
],
"tags": [
"Product Catalog"
],
"summary": "View Recent Catalog Uploads",
"description": "Make a call to this endpoint to list recent catalog uploads with their statuses to gain visibility into the ingestion workflow in order of creation. See the POST of this endpoint for details.\n",
"operationId": "getUploads",
"responses": {
"200": {
"description": "returns the list of uploads",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/CatalogUploadResponse"
}
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"
},
"429": {
"$ref": "#/components/responses/TooManyRequests"
},
"500": {
"$ref": "#/components/responses/InternalError"
}
}
}
},
"/product-catalog/uploads/{uploadId}": {
"x-external": true,
"description": "Making a GET request with the uploadID from your original catalog upload POST will give you the updated information on how the upload is progressing. You can also use the list endpoint as well to retrieve the same data. Please see the POST of `/product-catalog/uploads` for more detail.\n",
"get": {
"security": [
{
"OAuthFlow": [
"product_catalogs:read"
]
}
],
"summary": "Lookup Product Catalog Ingestion",
"tags": [
"Product Catalog"
],
"operationId": "lookupUpload",
"parameters": [
{
"name": "uploadId",
"in": "path",
"description": "The upload ID returned from a previous call",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Returns the provided upload status update",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CatalogUploadResponse"
}
}
}
},
"400": {
"$ref": "#/components/responses/InvalidParameter"
},
"401": {
"$ref": "#/components/responses/Unauthorized"
},
"403": {
"$ref": "#/components/responses/AccessDenied"
},
"404": {
"$ref": "#/components/responses/NotFound"