@foxtware/mineral 0.1.36 → 0.1.37

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.
Files changed (38) hide show
  1. package/.creds.yml.sample +9 -0
  2. package/api/marketingcloud/docs.md +6 -0
  3. package/api/marketingcloud/marketingcloud.constants.js +5 -0
  4. package/api/marketingcloud/marketingcloud.utils.js +67 -0
  5. package/api/marketingcloud/marketingcloudAttributesSearch.js +87 -0
  6. package/api/marketingcloud/marketingcloudAuthGet.js +111 -0
  7. package/api/marketingcloud/marketingcloudContactGet.js +79 -0
  8. package/api/marketingcloud/marketingcloudContactSearch.js +87 -0
  9. package/api/marketingcloud/marketingcloudSmsSubscriptionsGet.js +79 -0
  10. package/api/peoplevox/peoplevoxItemEdit.js +59 -22
  11. package/api/peoplevox/peoplevoxOrderEdit.js +49 -25
  12. package/api/shopify/shopifyInventoryItemUpdate.js +22 -16
  13. package/api/shopify/shopifyInventoryItemsUpdateBulk.js +79 -0
  14. package/api/shopify/shopifyMetafieldsSetBulk.js +87 -0
  15. package/api/shopify/shopifyProductGet.js +1 -0
  16. package/api/shopify/shopifyProductUpdate.js +136 -0
  17. package/api/shopify/shopifyProductUpdateTrigger.js +96 -0
  18. package/api/snowflake/docs.md +8 -2
  19. package/api/snowflake/snowflake.constants.js +19 -0
  20. package/api/snowflake/snowflake.utils.js +326 -0
  21. package/api/snowflake/snowflakeAuthGet.js +73 -0
  22. package/api/snowflake/snowflakeDatabasesGet.js +62 -0
  23. package/api/snowflake/snowflakeDynamicTablesGet.js +72 -0
  24. package/api/snowflake/snowflakeEventTablesGet.js +72 -0
  25. package/api/snowflake/snowflakeGet.js +153 -0
  26. package/api/snowflake/snowflakeIntegrationsGet.js +59 -0
  27. package/api/snowflake/snowflakeQuery.js +227 -0
  28. package/api/snowflake/snowflakeSchemasGet.js +69 -0
  29. package/api/snowflake/snowflakeStatementCancel.js +75 -0
  30. package/api/snowflake/snowflakeStatementExecute.js +105 -0
  31. package/api/snowflake/snowflakeStatementGet.js +80 -0
  32. package/api/snowflake/snowflakeTablesGet.js +72 -0
  33. package/api/snowflake/snowflakeUsersGet.js +60 -0
  34. package/api/snowflake/snowflakeWarehousesGet.js +43 -0
  35. package/api/starshipit/starshipitProductUpdate.js +1 -1
  36. package/api/starshipit/starshipitProductsGet.js +13 -6
  37. package/api/utils.js +62 -3
  38. package/package.json +1 -1
package/.creds.yml.sample CHANGED
@@ -140,6 +140,15 @@ salesforce:
140
140
  CLIENT_SECRET: _________________________
141
141
  LOGIN_URL: https://login.salesforce.com # or My Domain / test.salesforce.com
142
142
 
143
+ marketingcloud:
144
+ # Salesforce Marketing Cloud > Setup > Installed Packages > API Integration (server-to-server)
145
+ CLIENT_ID: _____________________________
146
+ CLIENT_SECRET: _________________________
147
+ AUTH_URL: https://________.auth.marketingcloudapis.com/
148
+ REST_URL: https://________.rest.marketingcloudapis.com/
149
+ SOAP_URL: https://________.soap.marketingcloudapis.com/ # optional
150
+ ACCOUNT_ID: ____________________________ # optional business unit MID
151
+
143
152
  fastsimon:
144
153
  store: # instance name of your choice
145
154
  UUID: ________________________________ # Dashboard → Settings
@@ -0,0 +1,6 @@
1
+ # Marketing Cloud Engagement
2
+
3
+ - [Developer documentation](https://developer.salesforce.com/docs/marketing/marketing-cloud/overview)
4
+ - [REST API](https://developer.salesforce.com/docs/marketing/marketing-cloud/references)
5
+
6
+ Tenant-specific auth and REST base URLs from Installed Packages → API Integration (server-to-server). OAuth 2.0 client credentials against `{subdomain}.auth.marketingcloudapis.com`, then REST calls to `{subdomain}.rest.marketingcloudapis.com`.
@@ -0,0 +1,5 @@
1
+ const TOKEN_PATH = '/v2/token';
2
+
3
+ module.exports = {
4
+ TOKEN_PATH,
5
+ };
@@ -0,0 +1,67 @@
1
+ const { marketingcloudAuthGet } = require('../marketingcloud/marketingcloudAuthGet');
2
+ const { resolveCreds } = require('../pipelineSteps');
3
+ const {
4
+ FetchClient,
5
+ appendUrlToBase,
6
+ fetchClientCommonSteps,
7
+ } = require('../utils');
8
+
9
+ const useRestUrlAndAuthHeaders = async (state) => {
10
+ const { requestPayload, context } = state;
11
+ const { creds } = context;
12
+
13
+ const authResponse = await marketingcloudAuthGet({
14
+ credsObject: creds,
15
+ });
16
+
17
+ if (!authResponse?.ok) {
18
+ return {
19
+ breakChain: true,
20
+ response: authResponse,
21
+ };
22
+ }
23
+
24
+ const {
25
+ access_token,
26
+ rest_instance_url,
27
+ } = authResponse.data;
28
+
29
+ const restBase = rest_instance_url || creds.REST_URL?.replace(/\/$/, '');
30
+
31
+ if (!restBase) {
32
+ return {
33
+ breakChain: true,
34
+ response: {
35
+ ok: false,
36
+ error: {
37
+ code: 'INVALID_CREDS',
38
+ message: 'Provide REST_URL in creds or ensure auth returns rest_instance_url.',
39
+ },
40
+ },
41
+ };
42
+ }
43
+
44
+ return {
45
+ requestPayload: {
46
+ ...requestPayload,
47
+ url: appendUrlToBase(restBase, requestPayload.url),
48
+ headers: {
49
+ Authorization: `Bearer ${ access_token }`,
50
+ ...requestPayload.headers,
51
+ },
52
+ },
53
+ };
54
+ };
55
+
56
+ const marketingcloudClient = new FetchClient({
57
+ pipeline: [
58
+ resolveCreds,
59
+ useRestUrlAndAuthHeaders,
60
+ 'fetch',
61
+ fetchClientCommonSteps.exitEarlyOnNotOk,
62
+ ],
63
+ });
64
+
65
+ module.exports = {
66
+ marketingcloudClient,
67
+ };
@@ -0,0 +1,87 @@
1
+ // https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_contacts/searchContactAttributes.html
2
+
3
+ const { ArgsWarden, valueProvided } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { marketingcloudClient } = require('../marketingcloud/marketingcloud.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ['conditionSet', valueProvided],
10
+ ]);
11
+
12
+ const marketingcloudAttributesSearch = async (
13
+ credsPayload,
14
+ conditionSet,
15
+ {
16
+ requestAttributes,
17
+ inspect = false,
18
+ fetchClient = marketingcloudClient,
19
+ } = {},
20
+ ) => {
21
+
22
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
23
+ credsPayload,
24
+ conditionSet,
25
+ });
26
+ if (rejectResponse) {
27
+ return rejectResponse;
28
+ }
29
+
30
+ const body = {
31
+ conditionSet,
32
+ };
33
+
34
+ if (requestAttributes) {
35
+ const attributes = Array.isArray(requestAttributes)
36
+ ? requestAttributes.map((item) => (
37
+ typeof item === 'string' ? { key: item } : item
38
+ ))
39
+ : requestAttributes;
40
+
41
+ body.request = { attributes };
42
+ }
43
+
44
+ return fetchClient.fetch({
45
+ requestPayload: {
46
+ method: 'post',
47
+ url: '/contacts/v1/attributes/search',
48
+ body,
49
+ },
50
+ context: {
51
+ credsPayload,
52
+ },
53
+ inspect,
54
+ });
55
+ };
56
+
57
+ const funcApiConfig = {
58
+ argsWarden,
59
+ };
60
+
61
+ module.exports = {
62
+ marketingcloudAttributesSearch,
63
+ funcApiConfig,
64
+ };
65
+
66
+ /*
67
+ curl -X POST "http://localhost:8000/marketingcloudAttributesSearch" \
68
+ -H "Content-Type: application/json" \
69
+ -d '{
70
+ "credsPayload": { "credsPath": "marketingcloud" },
71
+ "conditionSet": {
72
+ "operator": "And",
73
+ "conditionSets": [],
74
+ "conditions": [{
75
+ "attribute": { "key": "MobileConnect Demographics.Mobile Number" },
76
+ "operator": "Equals",
77
+ "value": { "items": ["61412345678"] }
78
+ }]
79
+ },
80
+ "options": {
81
+ "requestAttributes": [
82
+ "Contact.Contact Key",
83
+ "MobileConnect Demographics.Mobile Number"
84
+ ]
85
+ }
86
+ }'
87
+ */
@@ -0,0 +1,111 @@
1
+ // https://developer.salesforce.com/docs/marketing/marketing-cloud/guide/access-token-s2s.html
2
+
3
+ const {
4
+ ArgsWarden,
5
+ appendUrlToBase,
6
+ credsFromPayload,
7
+ customFetch,
8
+ } = require('../utils');
9
+ const { credsValidator } = require('../validators');
10
+ const { TOKEN_PATH } = require('../marketingcloud/marketingcloud.constants');
11
+
12
+ const argsWarden = new ArgsWarden([
13
+ ['credsPayload', credsValidator],
14
+ ]);
15
+
16
+ const marketingcloudAuthGet = async (
17
+ credsPayload,
18
+ ) => {
19
+
20
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
21
+ credsPayload,
22
+ });
23
+ if (rejectResponse) {
24
+ return rejectResponse;
25
+ }
26
+
27
+ const creds = await credsFromPayload(credsPayload);
28
+ const {
29
+ CLIENT_ID,
30
+ CLIENT_SECRET,
31
+ AUTH_URL,
32
+ ACCOUNT_ID,
33
+ } = creds ?? {};
34
+
35
+ if (!CLIENT_ID || !CLIENT_SECRET || !AUTH_URL) {
36
+ return {
37
+ ok: false,
38
+ error: {
39
+ code: 'INVALID_CREDS',
40
+ message: 'Provide CLIENT_ID, CLIENT_SECRET, and AUTH_URL for Marketing Cloud.',
41
+ },
42
+ };
43
+ }
44
+
45
+ const authBase = AUTH_URL.replace(/\/$/, '');
46
+ const tokenUrl = appendUrlToBase(authBase, TOKEN_PATH);
47
+
48
+ const body = {
49
+ grant_type: 'client_credentials',
50
+ client_id: CLIENT_ID,
51
+ client_secret: CLIENT_SECRET,
52
+ };
53
+
54
+ if (ACCOUNT_ID) {
55
+ body.account_id = ACCOUNT_ID;
56
+ }
57
+
58
+ const tokenResponse = await customFetch(tokenUrl, {
59
+ method: 'post',
60
+ body,
61
+ });
62
+
63
+ if (!tokenResponse.ok) {
64
+ return tokenResponse;
65
+ }
66
+
67
+ const {
68
+ access_token,
69
+ rest_instance_url,
70
+ soap_instance_url,
71
+ expires_in,
72
+ } = tokenResponse.data ?? {};
73
+
74
+ if (!access_token) {
75
+ return {
76
+ ok: false,
77
+ error: {
78
+ code: 'AUTH_FAILED',
79
+ message: 'Marketing Cloud did not return access_token.',
80
+ details: tokenResponse.data,
81
+ },
82
+ };
83
+ }
84
+
85
+ return {
86
+ ok: true,
87
+ data: {
88
+ access_token,
89
+ rest_instance_url: rest_instance_url || creds.REST_URL?.replace(/\/$/, ''),
90
+ soap_instance_url: soap_instance_url || creds.SOAP_URL?.replace(/\/$/, ''),
91
+ expires_in,
92
+ },
93
+ };
94
+ };
95
+
96
+ const funcApiConfig = {
97
+ argsWarden,
98
+ };
99
+
100
+ module.exports = {
101
+ marketingcloudAuthGet,
102
+ funcApiConfig,
103
+ };
104
+
105
+ /*
106
+ curl -X POST "http://localhost:8000/marketingcloudAuthGet" \
107
+ -H "Content-Type: application/json" \
108
+ -d '{
109
+ "credsPayload": { "credsPath": "marketingcloud" }
110
+ }'
111
+ */
@@ -0,0 +1,79 @@
1
+ // https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_contacts/getContact.html
2
+
3
+ const { ArgsWarden, actionSingleOrMultiple } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { marketingcloudClient } = require('../marketingcloud/marketingcloud.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ['contactKey'],
10
+ ]);
11
+
12
+ const marketingcloudContactGetSingle = async (
13
+ credsPayload,
14
+ contactKey,
15
+ {
16
+ inspect = false,
17
+ fetchClient = marketingcloudClient,
18
+ } = {},
19
+ ) => {
20
+
21
+ return fetchClient.fetch({
22
+ requestPayload: {
23
+ method: 'get',
24
+ url: `/contacts/v1/contacts/${ encodeURIComponent(contactKey) }`,
25
+ },
26
+ context: {
27
+ credsPayload,
28
+ },
29
+ inspect,
30
+ });
31
+ };
32
+
33
+ const marketingcloudContactGet = async (
34
+ credsPayload,
35
+ contactKey,
36
+ {
37
+ queueRunOptions,
38
+ ...options
39
+ } = {},
40
+ ) => {
41
+
42
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
43
+ credsPayload,
44
+ contactKey,
45
+ });
46
+ if (rejectResponse) {
47
+ return rejectResponse;
48
+ }
49
+
50
+ return actionSingleOrMultiple(
51
+ contactKey,
52
+ marketingcloudContactGetSingle,
53
+ (contactKeyItem) => ({
54
+ args: [credsPayload, contactKeyItem, options],
55
+ }),
56
+ {
57
+ ...(queueRunOptions ? { queueRunOptions } : {}),
58
+ },
59
+ );
60
+ };
61
+
62
+ const funcApiConfig = {
63
+ argsWarden,
64
+ };
65
+
66
+ module.exports = {
67
+ marketingcloudContactGet,
68
+ marketingcloudContactGetSingle,
69
+ funcApiConfig,
70
+ };
71
+
72
+ /*
73
+ curl -X POST "http://localhost:8000/marketingcloudContactGet" \
74
+ -H "Content-Type: application/json" \
75
+ -d '{
76
+ "credsPayload": { "credsPath": "marketingcloud" },
77
+ "contactKey": "1234567890"
78
+ }'
79
+ */
@@ -0,0 +1,87 @@
1
+ // https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_contacts/searchContacts.html
2
+
3
+ const { ArgsWarden, valueProvided } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { marketingcloudClient } = require('../marketingcloud/marketingcloud.utils');
6
+
7
+ const argsWarden = new ArgsWarden([
8
+ ['credsPayload', credsValidator],
9
+ ['conditionSet', valueProvided],
10
+ ]);
11
+
12
+ const marketingcloudContactSearch = async (
13
+ credsPayload,
14
+ conditionSet,
15
+ {
16
+ requestAttributes,
17
+ inspect = false,
18
+ fetchClient = marketingcloudClient,
19
+ } = {},
20
+ ) => {
21
+
22
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
23
+ credsPayload,
24
+ conditionSet,
25
+ });
26
+ if (rejectResponse) {
27
+ return rejectResponse;
28
+ }
29
+
30
+ const body = {
31
+ conditionSet,
32
+ };
33
+
34
+ if (requestAttributes) {
35
+ const attributes = Array.isArray(requestAttributes)
36
+ ? requestAttributes.map((item) => (
37
+ typeof item === 'string' ? { key: item } : item
38
+ ))
39
+ : requestAttributes;
40
+
41
+ body.request = { attributes };
42
+ }
43
+
44
+ return fetchClient.fetch({
45
+ requestPayload: {
46
+ method: 'post',
47
+ url: '/contacts/v1/contacts/search',
48
+ body,
49
+ },
50
+ context: {
51
+ credsPayload,
52
+ },
53
+ inspect,
54
+ });
55
+ };
56
+
57
+ const funcApiConfig = {
58
+ argsWarden,
59
+ };
60
+
61
+ module.exports = {
62
+ marketingcloudContactSearch,
63
+ funcApiConfig,
64
+ };
65
+
66
+ /*
67
+ curl -X POST "http://localhost:8000/marketingcloudContactSearch" \
68
+ -H "Content-Type: application/json" \
69
+ -d '{
70
+ "credsPayload": { "credsPath": "marketingcloud" },
71
+ "conditionSet": {
72
+ "operator": "And",
73
+ "conditionSets": [],
74
+ "conditions": [{
75
+ "attribute": { "key": "Email Addresses.Email Address" },
76
+ "operator": "Equals",
77
+ "value": { "items": ["user@example.com"] }
78
+ }]
79
+ },
80
+ "options": {
81
+ "requestAttributes": [
82
+ "Contact.Contact Key",
83
+ "Email Addresses.Email Address"
84
+ ]
85
+ }
86
+ }'
87
+ */
@@ -0,0 +1,79 @@
1
+ // https://developer.salesforce.com/docs/marketing/marketing-cloud/references/mc_rest_sms/contactsSubscriptions.html
2
+
3
+ const { ArgsWarden, ensureArray, objHasAny } = require('../utils');
4
+ const { credsValidator } = require('../validators');
5
+ const { marketingcloudClient } = require('../marketingcloud/marketingcloud.utils');
6
+
7
+ const subscriptionsValidator = (subscriptions) => {
8
+ return objHasAny(subscriptions, ['mobileNumber', 'subscriberKey']);
9
+ };
10
+
11
+ const argsWarden = new ArgsWarden([
12
+ ['credsPayload', credsValidator],
13
+ ['subscriptions', subscriptionsValidator],
14
+ ]);
15
+
16
+ const marketingcloudSmsSubscriptionsGet = async (
17
+ credsPayload,
18
+ subscriptions,
19
+ {
20
+ inspect = false,
21
+ fetchClient = marketingcloudClient,
22
+ } = {},
23
+ ) => {
24
+
25
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
26
+ credsPayload,
27
+ subscriptions,
28
+ });
29
+ if (rejectResponse) {
30
+ return rejectResponse;
31
+ }
32
+
33
+ const {
34
+ mobileNumber,
35
+ subscriberKey,
36
+ } = subscriptions;
37
+
38
+ const body = {};
39
+
40
+ if (mobileNumber) {
41
+ body.mobileNumber = ensureArray(mobileNumber);
42
+ }
43
+
44
+ if (subscriberKey) {
45
+ body.subscriberKey = ensureArray(subscriberKey);
46
+ }
47
+
48
+ return fetchClient.fetch({
49
+ requestPayload: {
50
+ method: 'post',
51
+ url: '/sms/v1/contacts/subscriptions',
52
+ body,
53
+ },
54
+ context: {
55
+ credsPayload,
56
+ },
57
+ inspect,
58
+ });
59
+ };
60
+
61
+ const funcApiConfig = {
62
+ argsWarden,
63
+ };
64
+
65
+ module.exports = {
66
+ marketingcloudSmsSubscriptionsGet,
67
+ funcApiConfig,
68
+ };
69
+
70
+ /*
71
+ curl -X POST "http://localhost:8000/marketingcloudSmsSubscriptionsGet" \
72
+ -H "Content-Type: application/json" \
73
+ -d '{
74
+ "credsPayload": { "credsPath": "marketingcloud" },
75
+ "subscriptions": {
76
+ "mobileNumber": ["61412345678", "61498765432"]
77
+ }
78
+ }'
79
+ */
@@ -1,6 +1,13 @@
1
1
  const { json2csv } = require('json-2-csv');
2
2
  const { credsValidator } = require('../validators');
3
- const { ensureArray, everyIfArray, ArgsWarden } = require('../utils');
3
+ const {
4
+ ensureArray,
5
+ everyIfArray,
6
+ ArgsWarden,
7
+ arrayToChunks,
8
+ groupObjectsByFields,
9
+ actionSingleOrMultiple,
10
+ } = require('../utils');
4
11
  const { peoplevoxClient } = require('../peoplevox/peoplevox.utils');
5
12
  const { MAX_REQUEST_ITEMS } = require('../peoplevox/peoplevox.constants');
6
13
 
@@ -11,33 +18,15 @@ const argsWarden = new ArgsWarden([
11
18
  ['itemPayload', (item) => everyIfArray(itemPayloadValidator, item)],
12
19
  ]);
13
20
 
14
- const peoplevoxItemEdit = async (
21
+ const peoplevoxItemEditChunk = async (
15
22
  credsPayload,
16
- itemPayload,
23
+ itemPayloads,
17
24
  {
18
25
  fetchClient = peoplevoxClient,
19
26
  } = {},
20
27
  ) => {
21
28
 
22
- const rejectResponse = await argsWarden.responseIfRejectingArgs({
23
- credsPayload,
24
- itemPayload,
25
- });
26
- if (rejectResponse) {
27
- return rejectResponse;
28
- }
29
-
30
- const csvData = await json2csv(ensureArray(itemPayload));
31
-
32
- if (csvData.length > MAX_REQUEST_ITEMS) {
33
- return {
34
- ok: false,
35
- error: {
36
- code: 'MAX_REQUEST_ITEMS_EXCEEDED',
37
- message: `Max request items exceeded. Max is ${ MAX_REQUEST_ITEMS }.`,
38
- },
39
- };
40
- }
29
+ const csvData = await json2csv(itemPayloads);
41
30
 
42
31
  return fetchClient.fetch({
43
32
  requestPayload: {
@@ -56,6 +45,43 @@ const peoplevoxItemEdit = async (
56
45
  });
57
46
  };
58
47
 
48
+ const peoplevoxItemEdit = async (
49
+ credsPayload,
50
+ itemPayload,
51
+ {
52
+ fetchClient = peoplevoxClient,
53
+ queueRunOptions,
54
+ } = {},
55
+ ) => {
56
+
57
+ const rejectResponse = await argsWarden.responseIfRejectingArgs({
58
+ credsPayload,
59
+ itemPayload,
60
+ });
61
+ if (rejectResponse) {
62
+ return rejectResponse;
63
+ }
64
+
65
+ const itemPayloads = ensureArray(itemPayload);
66
+
67
+ // Sort into buckets of matching field sets so json2csv does not pad missing keys,
68
+ // then chunk each bucket by max size
69
+ const buckets = groupObjectsByFields(itemPayloads);
70
+ const chunksByBucket = buckets.map((bucket) => arrayToChunks(bucket, MAX_REQUEST_ITEMS));
71
+ const chunks = chunksByBucket.flat();
72
+
73
+ return actionSingleOrMultiple(
74
+ chunks,
75
+ peoplevoxItemEditChunk,
76
+ (chunk) => ({
77
+ args: [credsPayload, chunk, { fetchClient }],
78
+ }),
79
+ {
80
+ ...(queueRunOptions ? { queueRunOptions } : {}),
81
+ },
82
+ );
83
+ };
84
+
59
85
  const funcApiConfig = {
60
86
  argsWarden,
61
87
  };
@@ -75,4 +101,15 @@ curl -X POST "http://localhost:8000/peoplevoxItemEdit" \
75
101
  "Attribute7": "ATTR7"
76
102
  }
77
103
  }'
104
+
105
+ Mixed field sets (grouped into separate SaveData calls):
106
+ curl -X POST "http://localhost:8000/peoplevoxItemEdit" \
107
+ -H "Content-Type: application/json" \
108
+ -d '{
109
+ "credsPayload": { "credsPath": "peoplevox" },
110
+ "itemPayload": [
111
+ { "ItemCode": "100335-CHC-L", "Attribute9": "Whatever" },
112
+ { "ItemCode": "100335-CHC-M", "Attribute9": "Watermelon", "Attribute10": "Werewolf" }
113
+ ]
114
+ }'
78
115
  */