@proveanything/smartlinks 2.0.39 → 2.0.40
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.
- package/dist/api/interactions.d.ts +14 -1
- package/dist/api/interactions.js +27 -0
- package/dist/docs/API_SUMMARY.md +24 -9
- package/dist/docs/interactions.md +65 -55
- package/dist/docs/overview.md +1 -1
- package/dist/openapi.yaml +17 -1
- package/dist/types/interaction.d.ts +16 -1
- package/docs/API_SUMMARY.md +24 -9
- package/docs/interactions.md +65 -55
- package/docs/overview.md +1 -1
- package/openapi.yaml +17 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AdminInteractionsCountsByOutcomeRequest, AdminInteractionsQueryRequest, AdminInteractionsAggregateRequest, AdminInteractionsAggregateResponse, AppendInteractionBody, UpdateInteractionBody, OutcomeCount, InteractionEventRow, PublicInteractionsCountsByOutcomeRequest, PublicInteractionsByUserRequest, SubmitInteractionResponse, SubmitInteractionError, CreateInteractionTypeBody, UpdateInteractionTypeBody, ListInteractionTypesQuery, InteractionTypeRecord, InteractionTypeList } from "../types/interaction.js";
|
|
1
|
+
import type { AdminInteractionsCountsByOutcomeRequest, AdminInteractionsQueryRequest, AdminInteractionsAggregateRequest, AdminInteractionsAggregateResponse, AppendInteractionBody, UpdateInteractionBody, OutcomeCount, InteractionEventRow, PublicInteractionsCountsByOutcomeRequest, PublicInteractionsByUserRequest, SubmitInteractionResponse, SubmitInteractionError, CreateInteractionTypeBody, EnsureInteractionTypeInput, UpdateInteractionTypeBody, ListInteractionTypesQuery, InteractionTypeRecord, InteractionTypeList } from "../types/interaction.js";
|
|
2
2
|
export declare namespace interactions {
|
|
3
3
|
/**
|
|
4
4
|
* POST /admin/collection/:collectionId/interactions/query
|
|
@@ -45,6 +45,19 @@ export declare namespace interactions {
|
|
|
45
45
|
function submitPublicEvent(collectionId: string, body: AppendInteractionBody): Promise<SubmitInteractionResponse | SubmitInteractionError>;
|
|
46
46
|
function create(collectionId: string, body: CreateInteractionTypeBody): Promise<InteractionTypeRecord>;
|
|
47
47
|
function list(collectionId: string, query?: ListInteractionTypesQuery): Promise<InteractionTypeList>;
|
|
48
|
+
/**
|
|
49
|
+
* Find this app's interaction type by its readable `key` (data.interactionType), or create it.
|
|
50
|
+
* Returns the type record — its `id` is the server-minted UUID every event must use.
|
|
51
|
+
* ADMIN only (run it in your admin/setup screen), then store the id in your app config so
|
|
52
|
+
* public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const vote = await SL.interactions.ensureType(collectionId, {
|
|
56
|
+
* appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true },
|
|
57
|
+
* })
|
|
58
|
+
* // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
59
|
+
*/
|
|
60
|
+
function ensureType(collectionId: string, input: EnsureInteractionTypeInput): Promise<InteractionTypeRecord>;
|
|
48
61
|
function get(collectionId: string, id: string): Promise<InteractionTypeRecord>;
|
|
49
62
|
function update(collectionId: string, id: string, patchBody: UpdateInteractionTypeBody): Promise<InteractionTypeRecord>;
|
|
50
63
|
function remove(collectionId: string, id: string): Promise<void>;
|
package/dist/api/interactions.js
CHANGED
|
@@ -102,6 +102,33 @@ export var interactions;
|
|
|
102
102
|
return request(path);
|
|
103
103
|
}
|
|
104
104
|
interactions.list = list;
|
|
105
|
+
/**
|
|
106
|
+
* Find this app's interaction type by its readable `key` (data.interactionType), or create it.
|
|
107
|
+
* Returns the type record — its `id` is the server-minted UUID every event must use.
|
|
108
|
+
* ADMIN only (run it in your admin/setup screen), then store the id in your app config so
|
|
109
|
+
* public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* const vote = await SL.interactions.ensureType(collectionId, {
|
|
113
|
+
* appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true },
|
|
114
|
+
* })
|
|
115
|
+
* // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
116
|
+
*/
|
|
117
|
+
async function ensureType(collectionId, input) {
|
|
118
|
+
const { appId, key, permissions, display, data } = input;
|
|
119
|
+
if (!appId || !key)
|
|
120
|
+
throw new Error('interactions.ensureType: appId and key are required');
|
|
121
|
+
for (let offset = 0;; offset += 200) {
|
|
122
|
+
const page = await list(collectionId, { appId, limit: 200, offset });
|
|
123
|
+
const found = (page.items || []).find((t) => t.data && t.data.interactionType === key);
|
|
124
|
+
if (found)
|
|
125
|
+
return found;
|
|
126
|
+
if (!page.items || page.items.length < 200)
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
return create(collectionId, Object.assign(Object.assign({ appId }, (permissions ? { permissions } : {})), { data: Object.assign(Object.assign(Object.assign({}, (data || {})), { interactionType: key }), (display ? { display } : {})) }));
|
|
130
|
+
}
|
|
131
|
+
interactions.ensureType = ensureType;
|
|
105
132
|
async function get(collectionId, id) {
|
|
106
133
|
const path = `/admin/collection/${encodeURIComponent(collectionId)}/interactions/${encodeURIComponent(id)}`;
|
|
107
134
|
return request(path);
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.40 | Generated: 2026-10-05T14:51:09.158Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -7275,13 +7275,24 @@ interface InteractionTypeList {
|
|
|
7275
7275
|
**CreateInteractionTypeBody** (interface)
|
|
7276
7276
|
```typescript
|
|
7277
7277
|
interface CreateInteractionTypeBody {
|
|
7278
|
-
id
|
|
7278
|
+
id?: string
|
|
7279
7279
|
appId: string
|
|
7280
7280
|
permissions?: InteractionPermissions
|
|
7281
7281
|
data?: Record<string, unknown>
|
|
7282
7282
|
}
|
|
7283
7283
|
```
|
|
7284
7284
|
|
|
7285
|
+
**EnsureInteractionTypeInput** (interface)
|
|
7286
|
+
```typescript
|
|
7287
|
+
interface EnsureInteractionTypeInput {
|
|
7288
|
+
appId: string
|
|
7289
|
+
key: string
|
|
7290
|
+
permissions?: InteractionPermissions
|
|
7291
|
+
display?: InteractionDisplay
|
|
7292
|
+
data?: Record<string, unknown>
|
|
7293
|
+
}
|
|
7294
|
+
```
|
|
7295
|
+
|
|
7285
7296
|
**UpdateInteractionTypeBody** (interface)
|
|
7286
7297
|
```typescript
|
|
7287
7298
|
interface UpdateInteractionTypeBody {
|
|
@@ -11325,36 +11336,40 @@ POST /api/v1/public/collection/:collectionId/interactions/submit Submits an inte
|
|
|
11325
11336
|
query: ListInteractionTypesQuery = {}) → `Promise<InteractionTypeList>`
|
|
11326
11337
|
POST /api/v1/public/collection/:collectionId/interactions/submit Submits an interaction event from a public/client-side context. `interactionId` must reference an existing interaction type definition. This endpoint does not create interaction definitions. When the interaction has `allowAnonymousSubmit: true`, neither `userId` nor `contactId` is required. Pass `anonId` inside `metadata` to enable device-level deduplication via `uniquePerAnonId`.
|
|
11327
11338
|
|
|
11339
|
+
**ensureType**(collectionId: string,
|
|
11340
|
+
input: EnsureInteractionTypeInput) → `Promise<InteractionTypeRecord>`
|
|
11341
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11342
|
+
|
|
11328
11343
|
**get**(collectionId: string,
|
|
11329
11344
|
id: string) → `Promise<InteractionTypeRecord>`
|
|
11330
|
-
|
|
11345
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11331
11346
|
|
|
11332
11347
|
**update**(collectionId: string,
|
|
11333
11348
|
id: string,
|
|
11334
11349
|
patchBody: UpdateInteractionTypeBody) → `Promise<InteractionTypeRecord>`
|
|
11335
|
-
|
|
11350
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11336
11351
|
|
|
11337
11352
|
**remove**(collectionId: string,
|
|
11338
11353
|
id: string) → `Promise<void>`
|
|
11339
|
-
|
|
11354
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11340
11355
|
|
|
11341
11356
|
**publicCountsByOutcome**(collectionId: string,
|
|
11342
11357
|
body: PublicInteractionsCountsByOutcomeRequest,
|
|
11343
11358
|
authToken?: string) → `Promise<OutcomeCount[]>`
|
|
11344
|
-
|
|
11359
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11345
11360
|
|
|
11346
11361
|
**publicMyInteractions**(collectionId: string,
|
|
11347
11362
|
body: PublicInteractionsByUserRequest,
|
|
11348
11363
|
authToken?: string) → `Promise<InteractionEventRow[]>`
|
|
11349
|
-
|
|
11364
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11350
11365
|
|
|
11351
11366
|
**publicList**(collectionId: string,
|
|
11352
11367
|
query: ListInteractionTypesQuery = {}) → `Promise<InteractionTypeList>`
|
|
11353
|
-
|
|
11368
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11354
11369
|
|
|
11355
11370
|
**publicGet**(collectionId: string,
|
|
11356
11371
|
id: string) → `Promise<InteractionTypeRecord>`
|
|
11357
|
-
|
|
11372
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11358
11373
|
|
|
11359
11374
|
### jobs
|
|
11360
11375
|
|
|
@@ -12,43 +12,50 @@ Interactions have two distinct layers:
|
|
|
12
12
|
|
|
13
13
|
| Layer | Purpose |
|
|
14
14
|
|-------|---------|
|
|
15
|
-
| **Interaction Types** | Definitions stored in the database
|
|
16
|
-
| **Interaction Events** | Individual event records logged each time a user performs that interaction |
|
|
15
|
+
| **Interaction Types** | Definitions stored in the database, once per collection: permissions, display metadata, effects. **The server mints each type's `id` (a UUID).** |
|
|
16
|
+
| **Interaction Events** | Individual event records logged each time a user performs that interaction. Each event's `interactionId` is a type's minted `id`. |
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## The one rule: create in admin → store the id in config → read it everywhere
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
**You never choose an interaction id, and you never write one as a string literal.** You choose a readable *key* (e.g. `'vote'`); the server mints the *id* (e.g. `'52bab6fa-…'`). Events must carry the id.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// 1. ADMIN / setup screen — find-or-create the type by your key, then store its minted id in app config.
|
|
24
|
+
const vote = await SL.interactions.ensureType(collectionId, {
|
|
25
|
+
appId: 'my-app',
|
|
26
|
+
key: 'vote', // your readable name — NOT the id
|
|
27
|
+
permissions: { allowPublicSubmit: true, uniquePerUser: true },
|
|
28
|
+
display: { title: 'Vote' },
|
|
29
|
+
});
|
|
30
|
+
const config = await SL.appConfiguration.getConfig({ collectionId, appId: 'my-app', admin: true });
|
|
31
|
+
await SL.appConfiguration.setConfig({
|
|
32
|
+
collectionId, appId: 'my-app', admin: true,
|
|
33
|
+
config: { ...config, interactionIds: { ...config?.interactionIds, vote: vote.id } },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 2. ANYWHERE ELSE (public widget, container, server function) — read the id from config.
|
|
37
|
+
const { interactionIds } = await SL.appConfiguration.getConfig({ collectionId, appId: 'my-app' });
|
|
38
|
+
await SL.interactions.submitPublicEvent(collectionId, {
|
|
39
|
+
appId: 'my-app',
|
|
40
|
+
interactionId: interactionIds.vote, // the minted id, from config
|
|
41
|
+
outcome: 'option-a',
|
|
42
|
+
});
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
Why it's split this way:
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
- **Types can only be created on the admin surface.** Public code (widgets, the portal) can submit events but can't create types — so the public side can only learn the id from somewhere admin put it. App config is that place.
|
|
48
|
+
- **`ensureType` is safe to re-run.** It returns the existing type when one with that `key` exists for your app, so run it every time your setup screen saves.
|
|
49
|
+
- **Unknown ids are configuration errors.** If `interactionIds.vote` is missing, the app hasn't been set up in this collection: show "not configured", don't submit, and never fall back to a literal like `'vote'`. (`interactions.create()` exists too, but it ignores any `id` you pass — read `id` from the record it returns.)
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
The examples below use `interactionIds` — the map you read from config in step 2.
|
|
45
52
|
|
|
46
53
|
---
|
|
47
54
|
|
|
48
55
|
## Common Use Cases
|
|
49
56
|
|
|
50
|
-
| Use Case | `
|
|
51
|
-
|
|
57
|
+
| Use Case | type `key` (for `ensureType`) | `outcome` example |
|
|
58
|
+
|----------|-------------------------------|-------------------|
|
|
52
59
|
| Competition entry | `competition-entry` | `"entered"` |
|
|
53
60
|
| Voting / polling | `vote` | `"option-a"` |
|
|
54
61
|
| Mailing list signup | `newsletter-signup` | `"subscribed"` |
|
|
@@ -62,12 +69,14 @@ If your app currently hardcodes strings like `"poll"` or `"entry"`, make sure th
|
|
|
62
69
|
|
|
63
70
|
Interaction types are defined once per collection and control permissions, display metadata, and uniqueness constraints.
|
|
64
71
|
|
|
65
|
-
### Create a Type
|
|
72
|
+
### Create a Type (find-or-create)
|
|
73
|
+
|
|
74
|
+
Admin only. Returns the type record; its `id` is the minted UUID — store it in app config (see [the one rule](#the-one-rule-create-in-admin--store-the-id-in-config--read-it-everywhere)).
|
|
66
75
|
|
|
67
76
|
```typescript
|
|
68
|
-
await SL.interactions.
|
|
69
|
-
id: 'vote',
|
|
77
|
+
const vote = await SL.interactions.ensureType(collectionId, {
|
|
70
78
|
appId: 'my-app',
|
|
79
|
+
key: 'vote', // stored as data.interactionType; how ensureType finds it again
|
|
71
80
|
permissions: {
|
|
72
81
|
allowPublicSubmit: true,
|
|
73
82
|
uniquePerUser: true,
|
|
@@ -75,35 +84,34 @@ await SL.interactions.create(collectionId, {
|
|
|
75
84
|
endAt: '2026-06-30T23:59:59Z',
|
|
76
85
|
allowPublicSummary: true,
|
|
77
86
|
},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
description: 'Cast your vote for the competition.',
|
|
82
|
-
},
|
|
87
|
+
display: {
|
|
88
|
+
title: 'Vote',
|
|
89
|
+
description: 'Cast your vote for the competition.',
|
|
83
90
|
},
|
|
84
91
|
});
|
|
92
|
+
// vote.id → '52bab6fa-d868-4616-9342-6731b0f332b2'
|
|
85
93
|
```
|
|
86
94
|
|
|
87
95
|
### Update / Delete a Type
|
|
88
96
|
|
|
89
97
|
```typescript
|
|
90
98
|
// Update permissions or display
|
|
91
|
-
await SL.interactions.update(collectionId,
|
|
99
|
+
await SL.interactions.update(collectionId, interactionIds.vote, {
|
|
92
100
|
permissions: { endAt: '2026-07-15T23:59:59Z' },
|
|
93
101
|
});
|
|
94
102
|
|
|
95
|
-
// Delete the definition (does not delete existing events)
|
|
96
|
-
await SL.interactions.remove(collectionId,
|
|
103
|
+
// Delete the definition (does not delete existing events) — and remove it from your config
|
|
104
|
+
await SL.interactions.remove(collectionId, interactionIds.vote);
|
|
97
105
|
```
|
|
98
106
|
|
|
99
107
|
### List / Get Types
|
|
100
108
|
|
|
101
109
|
```typescript
|
|
102
|
-
// Admin: list all types for an app
|
|
110
|
+
// Admin: list all types for an app (each has data.interactionType = its key)
|
|
103
111
|
const { items } = await SL.interactions.list(collectionId, { appId: 'my-app' });
|
|
104
112
|
|
|
105
113
|
// Admin: get a single type
|
|
106
|
-
const type = await SL.interactions.get(collectionId,
|
|
114
|
+
const type = await SL.interactions.get(collectionId, interactionIds.vote);
|
|
107
115
|
|
|
108
116
|
// Public: list available types (respects permissions)
|
|
109
117
|
const { items } = await SL.interactions.publicList(collectionId, { appId: 'my-app' });
|
|
@@ -113,7 +121,7 @@ const { items } = await SL.interactions.publicList(collectionId, { appId: 'my-ap
|
|
|
113
121
|
|
|
114
122
|
## Logging Events
|
|
115
123
|
|
|
116
|
-
|
|
124
|
+
Every `interactionId` below is a minted type id read from your app config (`interactionIds`, see [the one rule](#the-one-rule-create-in-admin--store-the-id-in-config--read-it-everywhere)). Events never create types: an id that isn't a type defined for this collection and app is a configuration error.
|
|
117
125
|
|
|
118
126
|
### Admin Event Append
|
|
119
127
|
|
|
@@ -122,7 +130,7 @@ Use on the server side or in admin flows. Requires `userId` **or** `contactId`.
|
|
|
122
130
|
```typescript
|
|
123
131
|
await SL.interactions.appendEvent(collectionId, {
|
|
124
132
|
appId: 'my-app',
|
|
125
|
-
interactionId:
|
|
133
|
+
interactionId: interactionIds.vote,
|
|
126
134
|
outcome: 'option-a', // The result / choice — used by countsByOutcome()
|
|
127
135
|
userId: 'user_abc123', // One of userId or contactId is required
|
|
128
136
|
productId: 'prod_xyz', // Optional — scope to a specific product
|
|
@@ -139,7 +147,7 @@ Use in client-side app code. Hits the public endpoint and respects interaction p
|
|
|
139
147
|
// Authenticated submission
|
|
140
148
|
await SL.interactions.submitPublicEvent(collectionId, {
|
|
141
149
|
appId: 'my-app',
|
|
142
|
-
interactionId:
|
|
150
|
+
interactionId: interactionIds.competitionEntry,
|
|
143
151
|
outcome: 'entered',
|
|
144
152
|
contactId: currentUser.contactId,
|
|
145
153
|
metadata: { answer: 'Paris' },
|
|
@@ -148,7 +156,7 @@ await SL.interactions.submitPublicEvent(collectionId, {
|
|
|
148
156
|
// Anonymous submission (interaction must have allowAnonymousSubmit: true)
|
|
149
157
|
const response = await SL.interactions.submitPublicEvent(collectionId, {
|
|
150
158
|
appId: 'my-app',
|
|
151
|
-
interactionId:
|
|
159
|
+
interactionId: interactionIds.npsScore,
|
|
152
160
|
outcome: '9',
|
|
153
161
|
metadata: {
|
|
154
162
|
anonId: SL.utils.getAnonId(), // device-level dedup signal
|
|
@@ -169,7 +177,7 @@ if (!response.success) {
|
|
|
169
177
|
```typescript
|
|
170
178
|
await SL.interactions.updateEvent(collectionId, {
|
|
171
179
|
eventId: 'evt_abc123', // Required — the event to update
|
|
172
|
-
interactionId:
|
|
180
|
+
interactionId: interactionIds.vote,
|
|
173
181
|
userId: 'user_abc123',
|
|
174
182
|
outcome: 'option-b', // Override the outcome
|
|
175
183
|
status: 'deleted', // Soft-delete the event
|
|
@@ -180,7 +188,7 @@ await SL.interactions.updateEvent(collectionId, {
|
|
|
180
188
|
|
|
181
189
|
| Field | Type | Required | Description |
|
|
182
190
|
|-------|------|----------|-------------|
|
|
183
|
-
| `interactionId` | string | ✅ |
|
|
191
|
+
| `interactionId` | string | ✅ | The type's minted `id` (from `ensureType`, stored in app config) — never a literal you made up |
|
|
184
192
|
| `userId` or `contactId` | string | ✅ (one of) | The actor. `appendEvent` / `updateEvent` require one of these |
|
|
185
193
|
| `appId` | string | ❌ | Scopes the event to your app |
|
|
186
194
|
| `outcome` | string | ❌ | The result or choice — what `countsByOutcome()` aggregates |
|
|
@@ -205,7 +213,7 @@ The primary analytics function — returns how many times each outcome was recor
|
|
|
205
213
|
// Admin (full access, deduplication options)
|
|
206
214
|
const results = await SL.interactions.countsByOutcome(collectionId, {
|
|
207
215
|
appId: 'my-app',
|
|
208
|
-
interactionId:
|
|
216
|
+
interactionId: interactionIds.vote,
|
|
209
217
|
scope: 'round-1', // Optional — filter by scope
|
|
210
218
|
from: '2026-06-01', // Optional — date range
|
|
211
219
|
to: '2026-06-30',
|
|
@@ -216,7 +224,7 @@ const results = await SL.interactions.countsByOutcome(collectionId, {
|
|
|
216
224
|
// Public (respects allowPublicSummary permission)
|
|
217
225
|
const results = await SL.interactions.publicCountsByOutcome(
|
|
218
226
|
collectionId,
|
|
219
|
-
{ appId: 'my-app', interactionId:
|
|
227
|
+
{ appId: 'my-app', interactionId: interactionIds.vote },
|
|
220
228
|
authToken // Optional — pass if user is authenticated
|
|
221
229
|
);
|
|
222
230
|
```
|
|
@@ -228,7 +236,7 @@ Flexible admin query for raw interaction events:
|
|
|
228
236
|
```typescript
|
|
229
237
|
const events = await SL.interactions.query(collectionId, {
|
|
230
238
|
appId: 'my-app',
|
|
231
|
-
interactionId:
|
|
239
|
+
interactionId: interactionIds.vote,
|
|
232
240
|
userId: 'user_abc123', // Filter by user
|
|
233
241
|
outcome: 'option-a', // Filter by outcome
|
|
234
242
|
from: '2026-06-01T00:00Z',
|
|
@@ -247,7 +255,7 @@ Lets authenticated users see their own events:
|
|
|
247
255
|
```typescript
|
|
248
256
|
const myEvents = await SL.interactions.publicMyInteractions(
|
|
249
257
|
collectionId,
|
|
250
|
-
{ appId: 'my-app', interactionId:
|
|
258
|
+
{ appId: 'my-app', interactionId: interactionIds.vote },
|
|
251
259
|
authToken
|
|
252
260
|
);
|
|
253
261
|
```
|
|
@@ -290,7 +298,7 @@ Platform emits event to Journey trigger
|
|
|
290
298
|
Journey step runs: send confirmation email, update CRM, award points, etc.
|
|
291
299
|
```
|
|
292
300
|
|
|
293
|
-
When defining a journey trigger, reference the `
|
|
301
|
+
When defining a journey trigger, reference the interaction type (its minted `id`) that should fire it. The interaction `outcome` and `metadata` are available as variables in journey steps.
|
|
294
302
|
|
|
295
303
|
---
|
|
296
304
|
|
|
@@ -316,9 +324,9 @@ A failure in one effect is logged and swallowed; subsequent effects still run.
|
|
|
316
324
|
Pass `data.effects` when creating or updating an interaction type:
|
|
317
325
|
|
|
318
326
|
```typescript
|
|
319
|
-
await SL.interactions.
|
|
320
|
-
id: 'donation',
|
|
327
|
+
await SL.interactions.ensureType(collectionId, {
|
|
321
328
|
appId: 'my-app',
|
|
329
|
+
key: 'donation',
|
|
322
330
|
permissions: { allowPublicSubmit: true },
|
|
323
331
|
data: {
|
|
324
332
|
effects: [
|
|
@@ -351,7 +359,7 @@ All effect `config` values support `{{token}}` interpolation resolved from the e
|
|
|
351
359
|
| `{{eventId}}` | BigQuery event UUID |
|
|
352
360
|
| `{{collectionId}}` | Collection / brand ID |
|
|
353
361
|
| `{{appId}}` | App ID that submitted the event |
|
|
354
|
-
| `{{interactionId}}` | Interaction
|
|
362
|
+
| `{{interactionId}}` | Interaction type id (the minted UUID) |
|
|
355
363
|
| `{{contactId}}` | Contact UUID (if resolved) |
|
|
356
364
|
| `{{userId}}` | Firebase UID (if authenticated) |
|
|
357
365
|
| `{{productId}}` | Product ID (if provided with event) |
|
|
@@ -496,7 +504,8 @@ import type {
|
|
|
496
504
|
InteractionPermissions, // Full permissions config shape
|
|
497
505
|
InteractionTypeRecord, // Definition record from create() / get()
|
|
498
506
|
InteractionTypeList, // { items, limit, offset }
|
|
499
|
-
CreateInteractionTypeBody, // Body for create()
|
|
507
|
+
CreateInteractionTypeBody, // Body for create() — the server mints the id
|
|
508
|
+
EnsureInteractionTypeInput, // Input for ensureType() — { appId, key, permissions?, display?, data? }
|
|
500
509
|
UpdateInteractionTypeBody, // Body for update()
|
|
501
510
|
AdminInteractionsQueryRequest, // query() filter options
|
|
502
511
|
AdminInteractionsCountsByOutcomeRequest,
|
|
@@ -520,7 +529,8 @@ import type {
|
|
|
520
529
|
|
|
521
530
|
## Best Practices
|
|
522
531
|
|
|
523
|
-
-
|
|
532
|
+
- Never hardcode an `interactionId`: create types with `ensureType` in admin, keep their ids in app config (`interactionIds.<key>`), read them everywhere else
|
|
533
|
+
- Use descriptive type keys: `warranty-registration`, `competition-entry`, `newsletter-vote`
|
|
524
534
|
- Use `outcome` to capture the choice — it's the key field that `countsByOutcome()` aggregates on
|
|
525
535
|
- Include `metadata` for richer analytics and debugging (`source`, `device`, `region`, etc.)
|
|
526
536
|
- Use `uniquePerUser: true` for actions that should only happen once (votes, registrations)
|
package/dist/docs/overview.md
CHANGED
|
@@ -280,7 +280,7 @@ See `docs/ai.md` for complete documentation.
|
|
|
280
280
|
|
|
281
281
|
The `SL.interactions` namespace tracks user engagement — competition entries, votes, form submissions, warranty registrations. Events can trigger automated journeys and communications.
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
**The one rule:** you never choose an interaction id. In admin, `SL.interactions.ensureType(collectionId, { appId, key: 'vote', … })` finds-or-creates the type and returns its server-minted `id`; store it in app config (`interactionIds: { vote: type.id }`); everywhere else — public widgets included — read `interactionIds.vote` from config and pass that as `interactionId`. Never write an interactionId as a string literal.
|
|
284
284
|
|
|
285
285
|
Key functions: `submitPublicEvent()`, `appendEvent()` (admin), `countsByOutcome()`, `query()`.
|
|
286
286
|
|
package/dist/openapi.yaml
CHANGED
|
@@ -26623,8 +26623,24 @@ components:
|
|
|
26623
26623
|
type: object
|
|
26624
26624
|
additionalProperties: true
|
|
26625
26625
|
required:
|
|
26626
|
-
- id
|
|
26627
26626
|
- appId
|
|
26627
|
+
EnsureInteractionTypeInput:
|
|
26628
|
+
type: object
|
|
26629
|
+
properties:
|
|
26630
|
+
appId:
|
|
26631
|
+
type: string
|
|
26632
|
+
key:
|
|
26633
|
+
type: string
|
|
26634
|
+
permissions:
|
|
26635
|
+
$ref: "#/components/schemas/InteractionPermissions"
|
|
26636
|
+
display:
|
|
26637
|
+
$ref: "#/components/schemas/InteractionDisplay"
|
|
26638
|
+
data:
|
|
26639
|
+
type: object
|
|
26640
|
+
additionalProperties: true
|
|
26641
|
+
required:
|
|
26642
|
+
- appId
|
|
26643
|
+
- key
|
|
26628
26644
|
UpdateInteractionTypeBody:
|
|
26629
26645
|
type: object
|
|
26630
26646
|
properties:
|
|
@@ -195,10 +195,25 @@ export interface InteractionTypeList {
|
|
|
195
195
|
limit: number;
|
|
196
196
|
offset: number;
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Body for interactions.create(). The SERVER mints the type's id (a UUID) — read it from the
|
|
200
|
+
* returned record. Prefer interactions.ensureType(), which also finds an existing type by its key.
|
|
201
|
+
*/
|
|
198
202
|
export interface CreateInteractionTypeBody {
|
|
199
|
-
id
|
|
203
|
+
/** @deprecated Ignored — the server mints the id. Use the `id` of the returned record (or ensureType). */
|
|
204
|
+
id?: string;
|
|
205
|
+
appId: string;
|
|
206
|
+
permissions?: InteractionPermissions;
|
|
207
|
+
data?: Record<string, unknown>;
|
|
208
|
+
}
|
|
209
|
+
/** Input for interactions.ensureType(): find this app's type by `key`, or create it. */
|
|
210
|
+
export interface EnsureInteractionTypeInput {
|
|
200
211
|
appId: string;
|
|
212
|
+
/** Your readable, stable name for the type, e.g. 'vote' — stored as data.interactionType. NOT the id. */
|
|
213
|
+
key: string;
|
|
201
214
|
permissions?: InteractionPermissions;
|
|
215
|
+
display?: InteractionDisplay;
|
|
216
|
+
/** Extra definition data (e.g. effects); merged under data alongside interactionType/display. */
|
|
202
217
|
data?: Record<string, unknown>;
|
|
203
218
|
}
|
|
204
219
|
export interface UpdateInteractionTypeBody {
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.40 | Generated: 2026-10-05T14:51:09.158Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -7275,13 +7275,24 @@ interface InteractionTypeList {
|
|
|
7275
7275
|
**CreateInteractionTypeBody** (interface)
|
|
7276
7276
|
```typescript
|
|
7277
7277
|
interface CreateInteractionTypeBody {
|
|
7278
|
-
id
|
|
7278
|
+
id?: string
|
|
7279
7279
|
appId: string
|
|
7280
7280
|
permissions?: InteractionPermissions
|
|
7281
7281
|
data?: Record<string, unknown>
|
|
7282
7282
|
}
|
|
7283
7283
|
```
|
|
7284
7284
|
|
|
7285
|
+
**EnsureInteractionTypeInput** (interface)
|
|
7286
|
+
```typescript
|
|
7287
|
+
interface EnsureInteractionTypeInput {
|
|
7288
|
+
appId: string
|
|
7289
|
+
key: string
|
|
7290
|
+
permissions?: InteractionPermissions
|
|
7291
|
+
display?: InteractionDisplay
|
|
7292
|
+
data?: Record<string, unknown>
|
|
7293
|
+
}
|
|
7294
|
+
```
|
|
7295
|
+
|
|
7285
7296
|
**UpdateInteractionTypeBody** (interface)
|
|
7286
7297
|
```typescript
|
|
7287
7298
|
interface UpdateInteractionTypeBody {
|
|
@@ -11325,36 +11336,40 @@ POST /api/v1/public/collection/:collectionId/interactions/submit Submits an inte
|
|
|
11325
11336
|
query: ListInteractionTypesQuery = {}) → `Promise<InteractionTypeList>`
|
|
11326
11337
|
POST /api/v1/public/collection/:collectionId/interactions/submit Submits an interaction event from a public/client-side context. `interactionId` must reference an existing interaction type definition. This endpoint does not create interaction definitions. When the interaction has `allowAnonymousSubmit: true`, neither `userId` nor `contactId` is required. Pass `anonId` inside `metadata` to enable device-level deduplication via `uniquePerAnonId`.
|
|
11327
11338
|
|
|
11339
|
+
**ensureType**(collectionId: string,
|
|
11340
|
+
input: EnsureInteractionTypeInput) → `Promise<InteractionTypeRecord>`
|
|
11341
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11342
|
+
|
|
11328
11343
|
**get**(collectionId: string,
|
|
11329
11344
|
id: string) → `Promise<InteractionTypeRecord>`
|
|
11330
|
-
|
|
11345
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11331
11346
|
|
|
11332
11347
|
**update**(collectionId: string,
|
|
11333
11348
|
id: string,
|
|
11334
11349
|
patchBody: UpdateInteractionTypeBody) → `Promise<InteractionTypeRecord>`
|
|
11335
|
-
|
|
11350
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11336
11351
|
|
|
11337
11352
|
**remove**(collectionId: string,
|
|
11338
11353
|
id: string) → `Promise<void>`
|
|
11339
|
-
|
|
11354
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11340
11355
|
|
|
11341
11356
|
**publicCountsByOutcome**(collectionId: string,
|
|
11342
11357
|
body: PublicInteractionsCountsByOutcomeRequest,
|
|
11343
11358
|
authToken?: string) → `Promise<OutcomeCount[]>`
|
|
11344
|
-
|
|
11359
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11345
11360
|
|
|
11346
11361
|
**publicMyInteractions**(collectionId: string,
|
|
11347
11362
|
body: PublicInteractionsByUserRequest,
|
|
11348
11363
|
authToken?: string) → `Promise<InteractionEventRow[]>`
|
|
11349
|
-
|
|
11364
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11350
11365
|
|
|
11351
11366
|
**publicList**(collectionId: string,
|
|
11352
11367
|
query: ListInteractionTypesQuery = {}) → `Promise<InteractionTypeList>`
|
|
11353
|
-
|
|
11368
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11354
11369
|
|
|
11355
11370
|
**publicGet**(collectionId: string,
|
|
11356
11371
|
id: string) → `Promise<InteractionTypeRecord>`
|
|
11357
|
-
|
|
11372
|
+
Find this app's interaction type by its readable `key` (data.interactionType), or create it. Returns the type record — its `id` is the server-minted UUID every event must use. ADMIN only (run it in your admin/setup screen), then store the id in your app config so public code can read it: `interactionIds: { vote: type.id }`. Safe to run on every setup. const vote = await SL.interactions.ensureType(collectionId, { appId: 'my-app', key: 'vote', permissions: { allowPublicSubmit: true, uniquePerUser: true }, }) // vote.id → '52bab6fa-…' — store it in config; never hardcode 'vote' as an interactionId
|
|
11358
11373
|
|
|
11359
11374
|
### jobs
|
|
11360
11375
|
|
package/docs/interactions.md
CHANGED
|
@@ -12,43 +12,50 @@ Interactions have two distinct layers:
|
|
|
12
12
|
|
|
13
13
|
| Layer | Purpose |
|
|
14
14
|
|-------|---------|
|
|
15
|
-
| **Interaction Types** | Definitions stored in the database
|
|
16
|
-
| **Interaction Events** | Individual event records logged each time a user performs that interaction |
|
|
15
|
+
| **Interaction Types** | Definitions stored in the database, once per collection: permissions, display metadata, effects. **The server mints each type's `id` (a UUID).** |
|
|
16
|
+
| **Interaction Events** | Individual event records logged each time a user performs that interaction. Each event's `interactionId` is a type's minted `id`. |
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## The one rule: create in admin → store the id in config → read it everywhere
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
**You never choose an interaction id, and you never write one as a string literal.** You choose a readable *key* (e.g. `'vote'`); the server mints the *id* (e.g. `'52bab6fa-…'`). Events must carry the id.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// 1. ADMIN / setup screen — find-or-create the type by your key, then store its minted id in app config.
|
|
24
|
+
const vote = await SL.interactions.ensureType(collectionId, {
|
|
25
|
+
appId: 'my-app',
|
|
26
|
+
key: 'vote', // your readable name — NOT the id
|
|
27
|
+
permissions: { allowPublicSubmit: true, uniquePerUser: true },
|
|
28
|
+
display: { title: 'Vote' },
|
|
29
|
+
});
|
|
30
|
+
const config = await SL.appConfiguration.getConfig({ collectionId, appId: 'my-app', admin: true });
|
|
31
|
+
await SL.appConfiguration.setConfig({
|
|
32
|
+
collectionId, appId: 'my-app', admin: true,
|
|
33
|
+
config: { ...config, interactionIds: { ...config?.interactionIds, vote: vote.id } },
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// 2. ANYWHERE ELSE (public widget, container, server function) — read the id from config.
|
|
37
|
+
const { interactionIds } = await SL.appConfiguration.getConfig({ collectionId, appId: 'my-app' });
|
|
38
|
+
await SL.interactions.submitPublicEvent(collectionId, {
|
|
39
|
+
appId: 'my-app',
|
|
40
|
+
interactionId: interactionIds.vote, // the minted id, from config
|
|
41
|
+
outcome: 'option-a',
|
|
42
|
+
});
|
|
36
43
|
```
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
Why it's split this way:
|
|
39
46
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
- **Types can only be created on the admin surface.** Public code (widgets, the portal) can submit events but can't create types — so the public side can only learn the id from somewhere admin put it. App config is that place.
|
|
48
|
+
- **`ensureType` is safe to re-run.** It returns the existing type when one with that `key` exists for your app, so run it every time your setup screen saves.
|
|
49
|
+
- **Unknown ids are configuration errors.** If `interactionIds.vote` is missing, the app hasn't been set up in this collection: show "not configured", don't submit, and never fall back to a literal like `'vote'`. (`interactions.create()` exists too, but it ignores any `id` you pass — read `id` from the record it returns.)
|
|
43
50
|
|
|
44
|
-
|
|
51
|
+
The examples below use `interactionIds` — the map you read from config in step 2.
|
|
45
52
|
|
|
46
53
|
---
|
|
47
54
|
|
|
48
55
|
## Common Use Cases
|
|
49
56
|
|
|
50
|
-
| Use Case | `
|
|
51
|
-
|
|
57
|
+
| Use Case | type `key` (for `ensureType`) | `outcome` example |
|
|
58
|
+
|----------|-------------------------------|-------------------|
|
|
52
59
|
| Competition entry | `competition-entry` | `"entered"` |
|
|
53
60
|
| Voting / polling | `vote` | `"option-a"` |
|
|
54
61
|
| Mailing list signup | `newsletter-signup` | `"subscribed"` |
|
|
@@ -62,12 +69,14 @@ If your app currently hardcodes strings like `"poll"` or `"entry"`, make sure th
|
|
|
62
69
|
|
|
63
70
|
Interaction types are defined once per collection and control permissions, display metadata, and uniqueness constraints.
|
|
64
71
|
|
|
65
|
-
### Create a Type
|
|
72
|
+
### Create a Type (find-or-create)
|
|
73
|
+
|
|
74
|
+
Admin only. Returns the type record; its `id` is the minted UUID — store it in app config (see [the one rule](#the-one-rule-create-in-admin--store-the-id-in-config--read-it-everywhere)).
|
|
66
75
|
|
|
67
76
|
```typescript
|
|
68
|
-
await SL.interactions.
|
|
69
|
-
id: 'vote',
|
|
77
|
+
const vote = await SL.interactions.ensureType(collectionId, {
|
|
70
78
|
appId: 'my-app',
|
|
79
|
+
key: 'vote', // stored as data.interactionType; how ensureType finds it again
|
|
71
80
|
permissions: {
|
|
72
81
|
allowPublicSubmit: true,
|
|
73
82
|
uniquePerUser: true,
|
|
@@ -75,35 +84,34 @@ await SL.interactions.create(collectionId, {
|
|
|
75
84
|
endAt: '2026-06-30T23:59:59Z',
|
|
76
85
|
allowPublicSummary: true,
|
|
77
86
|
},
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
description: 'Cast your vote for the competition.',
|
|
82
|
-
},
|
|
87
|
+
display: {
|
|
88
|
+
title: 'Vote',
|
|
89
|
+
description: 'Cast your vote for the competition.',
|
|
83
90
|
},
|
|
84
91
|
});
|
|
92
|
+
// vote.id → '52bab6fa-d868-4616-9342-6731b0f332b2'
|
|
85
93
|
```
|
|
86
94
|
|
|
87
95
|
### Update / Delete a Type
|
|
88
96
|
|
|
89
97
|
```typescript
|
|
90
98
|
// Update permissions or display
|
|
91
|
-
await SL.interactions.update(collectionId,
|
|
99
|
+
await SL.interactions.update(collectionId, interactionIds.vote, {
|
|
92
100
|
permissions: { endAt: '2026-07-15T23:59:59Z' },
|
|
93
101
|
});
|
|
94
102
|
|
|
95
|
-
// Delete the definition (does not delete existing events)
|
|
96
|
-
await SL.interactions.remove(collectionId,
|
|
103
|
+
// Delete the definition (does not delete existing events) — and remove it from your config
|
|
104
|
+
await SL.interactions.remove(collectionId, interactionIds.vote);
|
|
97
105
|
```
|
|
98
106
|
|
|
99
107
|
### List / Get Types
|
|
100
108
|
|
|
101
109
|
```typescript
|
|
102
|
-
// Admin: list all types for an app
|
|
110
|
+
// Admin: list all types for an app (each has data.interactionType = its key)
|
|
103
111
|
const { items } = await SL.interactions.list(collectionId, { appId: 'my-app' });
|
|
104
112
|
|
|
105
113
|
// Admin: get a single type
|
|
106
|
-
const type = await SL.interactions.get(collectionId,
|
|
114
|
+
const type = await SL.interactions.get(collectionId, interactionIds.vote);
|
|
107
115
|
|
|
108
116
|
// Public: list available types (respects permissions)
|
|
109
117
|
const { items } = await SL.interactions.publicList(collectionId, { appId: 'my-app' });
|
|
@@ -113,7 +121,7 @@ const { items } = await SL.interactions.publicList(collectionId, { appId: 'my-ap
|
|
|
113
121
|
|
|
114
122
|
## Logging Events
|
|
115
123
|
|
|
116
|
-
|
|
124
|
+
Every `interactionId` below is a minted type id read from your app config (`interactionIds`, see [the one rule](#the-one-rule-create-in-admin--store-the-id-in-config--read-it-everywhere)). Events never create types: an id that isn't a type defined for this collection and app is a configuration error.
|
|
117
125
|
|
|
118
126
|
### Admin Event Append
|
|
119
127
|
|
|
@@ -122,7 +130,7 @@ Use on the server side or in admin flows. Requires `userId` **or** `contactId`.
|
|
|
122
130
|
```typescript
|
|
123
131
|
await SL.interactions.appendEvent(collectionId, {
|
|
124
132
|
appId: 'my-app',
|
|
125
|
-
interactionId:
|
|
133
|
+
interactionId: interactionIds.vote,
|
|
126
134
|
outcome: 'option-a', // The result / choice — used by countsByOutcome()
|
|
127
135
|
userId: 'user_abc123', // One of userId or contactId is required
|
|
128
136
|
productId: 'prod_xyz', // Optional — scope to a specific product
|
|
@@ -139,7 +147,7 @@ Use in client-side app code. Hits the public endpoint and respects interaction p
|
|
|
139
147
|
// Authenticated submission
|
|
140
148
|
await SL.interactions.submitPublicEvent(collectionId, {
|
|
141
149
|
appId: 'my-app',
|
|
142
|
-
interactionId:
|
|
150
|
+
interactionId: interactionIds.competitionEntry,
|
|
143
151
|
outcome: 'entered',
|
|
144
152
|
contactId: currentUser.contactId,
|
|
145
153
|
metadata: { answer: 'Paris' },
|
|
@@ -148,7 +156,7 @@ await SL.interactions.submitPublicEvent(collectionId, {
|
|
|
148
156
|
// Anonymous submission (interaction must have allowAnonymousSubmit: true)
|
|
149
157
|
const response = await SL.interactions.submitPublicEvent(collectionId, {
|
|
150
158
|
appId: 'my-app',
|
|
151
|
-
interactionId:
|
|
159
|
+
interactionId: interactionIds.npsScore,
|
|
152
160
|
outcome: '9',
|
|
153
161
|
metadata: {
|
|
154
162
|
anonId: SL.utils.getAnonId(), // device-level dedup signal
|
|
@@ -169,7 +177,7 @@ if (!response.success) {
|
|
|
169
177
|
```typescript
|
|
170
178
|
await SL.interactions.updateEvent(collectionId, {
|
|
171
179
|
eventId: 'evt_abc123', // Required — the event to update
|
|
172
|
-
interactionId:
|
|
180
|
+
interactionId: interactionIds.vote,
|
|
173
181
|
userId: 'user_abc123',
|
|
174
182
|
outcome: 'option-b', // Override the outcome
|
|
175
183
|
status: 'deleted', // Soft-delete the event
|
|
@@ -180,7 +188,7 @@ await SL.interactions.updateEvent(collectionId, {
|
|
|
180
188
|
|
|
181
189
|
| Field | Type | Required | Description |
|
|
182
190
|
|-------|------|----------|-------------|
|
|
183
|
-
| `interactionId` | string | ✅ |
|
|
191
|
+
| `interactionId` | string | ✅ | The type's minted `id` (from `ensureType`, stored in app config) — never a literal you made up |
|
|
184
192
|
| `userId` or `contactId` | string | ✅ (one of) | The actor. `appendEvent` / `updateEvent` require one of these |
|
|
185
193
|
| `appId` | string | ❌ | Scopes the event to your app |
|
|
186
194
|
| `outcome` | string | ❌ | The result or choice — what `countsByOutcome()` aggregates |
|
|
@@ -205,7 +213,7 @@ The primary analytics function — returns how many times each outcome was recor
|
|
|
205
213
|
// Admin (full access, deduplication options)
|
|
206
214
|
const results = await SL.interactions.countsByOutcome(collectionId, {
|
|
207
215
|
appId: 'my-app',
|
|
208
|
-
interactionId:
|
|
216
|
+
interactionId: interactionIds.vote,
|
|
209
217
|
scope: 'round-1', // Optional — filter by scope
|
|
210
218
|
from: '2026-06-01', // Optional — date range
|
|
211
219
|
to: '2026-06-30',
|
|
@@ -216,7 +224,7 @@ const results = await SL.interactions.countsByOutcome(collectionId, {
|
|
|
216
224
|
// Public (respects allowPublicSummary permission)
|
|
217
225
|
const results = await SL.interactions.publicCountsByOutcome(
|
|
218
226
|
collectionId,
|
|
219
|
-
{ appId: 'my-app', interactionId:
|
|
227
|
+
{ appId: 'my-app', interactionId: interactionIds.vote },
|
|
220
228
|
authToken // Optional — pass if user is authenticated
|
|
221
229
|
);
|
|
222
230
|
```
|
|
@@ -228,7 +236,7 @@ Flexible admin query for raw interaction events:
|
|
|
228
236
|
```typescript
|
|
229
237
|
const events = await SL.interactions.query(collectionId, {
|
|
230
238
|
appId: 'my-app',
|
|
231
|
-
interactionId:
|
|
239
|
+
interactionId: interactionIds.vote,
|
|
232
240
|
userId: 'user_abc123', // Filter by user
|
|
233
241
|
outcome: 'option-a', // Filter by outcome
|
|
234
242
|
from: '2026-06-01T00:00Z',
|
|
@@ -247,7 +255,7 @@ Lets authenticated users see their own events:
|
|
|
247
255
|
```typescript
|
|
248
256
|
const myEvents = await SL.interactions.publicMyInteractions(
|
|
249
257
|
collectionId,
|
|
250
|
-
{ appId: 'my-app', interactionId:
|
|
258
|
+
{ appId: 'my-app', interactionId: interactionIds.vote },
|
|
251
259
|
authToken
|
|
252
260
|
);
|
|
253
261
|
```
|
|
@@ -290,7 +298,7 @@ Platform emits event to Journey trigger
|
|
|
290
298
|
Journey step runs: send confirmation email, update CRM, award points, etc.
|
|
291
299
|
```
|
|
292
300
|
|
|
293
|
-
When defining a journey trigger, reference the `
|
|
301
|
+
When defining a journey trigger, reference the interaction type (its minted `id`) that should fire it. The interaction `outcome` and `metadata` are available as variables in journey steps.
|
|
294
302
|
|
|
295
303
|
---
|
|
296
304
|
|
|
@@ -316,9 +324,9 @@ A failure in one effect is logged and swallowed; subsequent effects still run.
|
|
|
316
324
|
Pass `data.effects` when creating or updating an interaction type:
|
|
317
325
|
|
|
318
326
|
```typescript
|
|
319
|
-
await SL.interactions.
|
|
320
|
-
id: 'donation',
|
|
327
|
+
await SL.interactions.ensureType(collectionId, {
|
|
321
328
|
appId: 'my-app',
|
|
329
|
+
key: 'donation',
|
|
322
330
|
permissions: { allowPublicSubmit: true },
|
|
323
331
|
data: {
|
|
324
332
|
effects: [
|
|
@@ -351,7 +359,7 @@ All effect `config` values support `{{token}}` interpolation resolved from the e
|
|
|
351
359
|
| `{{eventId}}` | BigQuery event UUID |
|
|
352
360
|
| `{{collectionId}}` | Collection / brand ID |
|
|
353
361
|
| `{{appId}}` | App ID that submitted the event |
|
|
354
|
-
| `{{interactionId}}` | Interaction
|
|
362
|
+
| `{{interactionId}}` | Interaction type id (the minted UUID) |
|
|
355
363
|
| `{{contactId}}` | Contact UUID (if resolved) |
|
|
356
364
|
| `{{userId}}` | Firebase UID (if authenticated) |
|
|
357
365
|
| `{{productId}}` | Product ID (if provided with event) |
|
|
@@ -496,7 +504,8 @@ import type {
|
|
|
496
504
|
InteractionPermissions, // Full permissions config shape
|
|
497
505
|
InteractionTypeRecord, // Definition record from create() / get()
|
|
498
506
|
InteractionTypeList, // { items, limit, offset }
|
|
499
|
-
CreateInteractionTypeBody, // Body for create()
|
|
507
|
+
CreateInteractionTypeBody, // Body for create() — the server mints the id
|
|
508
|
+
EnsureInteractionTypeInput, // Input for ensureType() — { appId, key, permissions?, display?, data? }
|
|
500
509
|
UpdateInteractionTypeBody, // Body for update()
|
|
501
510
|
AdminInteractionsQueryRequest, // query() filter options
|
|
502
511
|
AdminInteractionsCountsByOutcomeRequest,
|
|
@@ -520,7 +529,8 @@ import type {
|
|
|
520
529
|
|
|
521
530
|
## Best Practices
|
|
522
531
|
|
|
523
|
-
-
|
|
532
|
+
- Never hardcode an `interactionId`: create types with `ensureType` in admin, keep their ids in app config (`interactionIds.<key>`), read them everywhere else
|
|
533
|
+
- Use descriptive type keys: `warranty-registration`, `competition-entry`, `newsletter-vote`
|
|
524
534
|
- Use `outcome` to capture the choice — it's the key field that `countsByOutcome()` aggregates on
|
|
525
535
|
- Include `metadata` for richer analytics and debugging (`source`, `device`, `region`, etc.)
|
|
526
536
|
- Use `uniquePerUser: true` for actions that should only happen once (votes, registrations)
|
package/docs/overview.md
CHANGED
|
@@ -280,7 +280,7 @@ See `docs/ai.md` for complete documentation.
|
|
|
280
280
|
|
|
281
281
|
The `SL.interactions` namespace tracks user engagement — competition entries, votes, form submissions, warranty registrations. Events can trigger automated journeys and communications.
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
**The one rule:** you never choose an interaction id. In admin, `SL.interactions.ensureType(collectionId, { appId, key: 'vote', … })` finds-or-creates the type and returns its server-minted `id`; store it in app config (`interactionIds: { vote: type.id }`); everywhere else — public widgets included — read `interactionIds.vote` from config and pass that as `interactionId`. Never write an interactionId as a string literal.
|
|
284
284
|
|
|
285
285
|
Key functions: `submitPublicEvent()`, `appendEvent()` (admin), `countsByOutcome()`, `query()`.
|
|
286
286
|
|
package/openapi.yaml
CHANGED
|
@@ -26623,8 +26623,24 @@ components:
|
|
|
26623
26623
|
type: object
|
|
26624
26624
|
additionalProperties: true
|
|
26625
26625
|
required:
|
|
26626
|
-
- id
|
|
26627
26626
|
- appId
|
|
26627
|
+
EnsureInteractionTypeInput:
|
|
26628
|
+
type: object
|
|
26629
|
+
properties:
|
|
26630
|
+
appId:
|
|
26631
|
+
type: string
|
|
26632
|
+
key:
|
|
26633
|
+
type: string
|
|
26634
|
+
permissions:
|
|
26635
|
+
$ref: "#/components/schemas/InteractionPermissions"
|
|
26636
|
+
display:
|
|
26637
|
+
$ref: "#/components/schemas/InteractionDisplay"
|
|
26638
|
+
data:
|
|
26639
|
+
type: object
|
|
26640
|
+
additionalProperties: true
|
|
26641
|
+
required:
|
|
26642
|
+
- appId
|
|
26643
|
+
- key
|
|
26628
26644
|
UpdateInteractionTypeBody:
|
|
26629
26645
|
type: object
|
|
26630
26646
|
properties:
|