@proveanything/smartlinks 2.1.14 → 2.1.15

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.
@@ -105,4 +105,32 @@ export declare namespace integrations {
105
105
  function runConnectorStep<T = unknown>(collectionId: string, appId: string, step: ConnectorConsoleStep, input?: Record<string, unknown> & {
106
106
  flowId?: string;
107
107
  }): Promise<T>;
108
+ /**
109
+ * Invoke a connector ACTION from the console (any visibility): redeem, enrol, adjust points, issue a
110
+ * coupon… The connector's function runs with its own credentials and is told who asked.
111
+ * POST /integrations/connectors/:appId/actions/:action { input }
112
+ */
113
+ function runConnectorAction<T = unknown>(collectionId: string, appId: string, action: string, input?: Record<string, unknown>): Promise<T>;
114
+ /**
115
+ * PUBLIC (the Hub, a site): the connector actions the caller may invoke — those declared `public`,
116
+ * plus `member` ones when signed in.
117
+ * GET /public/collection/:collectionId/connectors/:appId/actions
118
+ */
119
+ function listConnectorActions(collectionId: string, appId: string): Promise<{
120
+ actions: Array<{
121
+ name: string;
122
+ title: string;
123
+ description?: string;
124
+ visibility: 'public' | 'member' | 'admin';
125
+ input?: Record<string, unknown>;
126
+ output?: Record<string, unknown>;
127
+ }>;
128
+ }>;
129
+ /**
130
+ * PUBLIC (the Hub, a site): invoke a connector action — e.g. redeem a reward for the signed-in member.
131
+ * 401 SIGN_IN_REQUIRED for a `member` action without a session; 403 for an admin-only one;
132
+ * 400 with `missing[]` when a required input is absent.
133
+ * POST /public/collection/:collectionId/connectors/:appId/actions/:action { input }
134
+ */
135
+ function callConnectorAction<T = unknown>(collectionId: string, appId: string, action: string, input?: Record<string, unknown>): Promise<T>;
108
136
  }
@@ -200,4 +200,32 @@ export var integrations;
200
200
  return post(`${connectorsBase(collectionId)}/${enc(appId)}/steps/${enc(step)}`, input);
201
201
  }
202
202
  integrations.runConnectorStep = runConnectorStep;
203
+ /**
204
+ * Invoke a connector ACTION from the console (any visibility): redeem, enrol, adjust points, issue a
205
+ * coupon… The connector's function runs with its own credentials and is told who asked.
206
+ * POST /integrations/connectors/:appId/actions/:action { input }
207
+ */
208
+ async function runConnectorAction(collectionId, appId, action, input = {}) {
209
+ return post(`${connectorsBase(collectionId)}/${enc(appId)}/actions/${enc(action)}`, { input });
210
+ }
211
+ integrations.runConnectorAction = runConnectorAction;
212
+ /**
213
+ * PUBLIC (the Hub, a site): the connector actions the caller may invoke — those declared `public`,
214
+ * plus `member` ones when signed in.
215
+ * GET /public/collection/:collectionId/connectors/:appId/actions
216
+ */
217
+ async function listConnectorActions(collectionId, appId) {
218
+ return request(`/public/collection/${enc(collectionId)}/connectors/${enc(appId)}/actions`);
219
+ }
220
+ integrations.listConnectorActions = listConnectorActions;
221
+ /**
222
+ * PUBLIC (the Hub, a site): invoke a connector action — e.g. redeem a reward for the signed-in member.
223
+ * 401 SIGN_IN_REQUIRED for a `member` action without a session; 403 for an admin-only one;
224
+ * 400 with `missing[]` when a required input is absent.
225
+ * POST /public/collection/:collectionId/connectors/:appId/actions/:action { input }
226
+ */
227
+ async function callConnectorAction(collectionId, appId, action, input = {}) {
228
+ return post(`/public/collection/${enc(collectionId)}/connectors/${enc(appId)}/actions/${enc(action)}`, { input });
229
+ }
230
+ integrations.callConnectorAction = callConnectorAction;
203
231
  })(integrations || (integrations = {}));
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.1.14 | Generated: 2026-11-09T13:01:05.900Z
3
+ Version: 2.1.15 | Generated: 2026-11-09T15:17:42.472Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -65,7 +65,7 @@ For detailed guides on specific features:
65
65
  - **[Analytics Metadata Conventions](analytics-metadata-conventions.md)** - Standard recommended keys and conventions for analytics metadata fields
66
66
  - **[Loyalty: Points, Members & Earning Rules](loyalty.md)** - Loyalty schemes, automatic point earning via interaction rules, member balances, transaction history, and manual adjustments
67
67
  - **[Integrations](integrations.md)** - Inbound/outbound integration flows + the sealed-secret store; triggers (manual/event/schedule), event patterns, the event envelope (interaction/contact/event sources), identity policy, event mapping, signed deliveries, contact as a target, field mappings
68
- - **[Connectors](connectors.md)** - Connector apps (adapters for one external provider): a manifest `connector` block + server functions (testConnection/describeTarget/sample/fetch/shape/serialize/send/describeEvents/lookup); loyalty and CRM connectors; core does the mapping
68
+ - **[Connectors](connectors.md)** - Connector apps (adapters for one external provider): a manifest `connector` block + server functions (testConnection/describeTarget/sample/fetch/shape/serialize/send/describeEvents/lookup); loyalty and CRM connectors; actions (provider operations on demand); core does the mapping
69
69
  - **[Entitlements](entitlements.md)** - "Is this person gold or above?" answered on demand from signed claims, a connector lookup (cached), or the contact — never a mirrored member base; gates on interaction types and in server functions
70
70
  - **[AI Tools & Skills](ai-tools-and-skills.md)** - The AI capability catalog: skills apps invoke by name (e.g. research.brand), the tools the AI reaches for (web fetch/screenshot/brand assets/image gen), the agent loop, and how apps discover them
71
71
  - **[Deep Link Discovery](deep-link-discovery.md)** - Registering and discovering navigable app states for portal menus and AI orchestration
@@ -3112,6 +3112,19 @@ interface ConnectorPanel {
3112
3112
  }
3113
3113
  ```
3114
3114
 
3115
+ **ConnectorAction** (interface)
3116
+ ```typescript
3117
+ interface ConnectorAction {
3118
+ name: string
3119
+ title?: string
3120
+ description?: string
3121
+ function: string
3122
+ visibility?: 'admin' | 'member' | 'public'
3123
+ input?: Record<string, unknown>
3124
+ output?: Record<string, unknown>
3125
+ }
3126
+ ```
3127
+
3115
3128
  **AppConnectorDeclaration** (interface)
3116
3129
  ```typescript
3117
3130
  interface AppConnectorDeclaration {
@@ -3134,6 +3147,7 @@ interface AppConnectorDeclaration {
3134
3147
  lookup?: string
3135
3148
  }
3136
3149
  panels?: ConnectorPanel[]
3150
+ actions?: ConnectorAction[]
3137
3151
  aiGuide?: string
3138
3152
  recordId?: string
3139
3153
  limits?: { requestsPerSecond?: number; maxEvents?: number; maxAttributes?: number }
@@ -7980,6 +7994,7 @@ interface IntegrationFlowConfig {
7980
7994
  signing?: { credentialRef: string; previousCredentialRef?: string }
7981
7995
  identity?: { policy?: IdentityPolicy; hashKeyCredentialRef?: string }
7982
7996
  eventMapping?: EventMapping
7997
+ envelopeFormat?: 'cloudevents'
7983
7998
  connection?: FlowConnection
7984
7999
  fieldMappings?: FieldMapping[]
7985
8000
  connector?: FlowConnectorRef
@@ -8222,6 +8237,7 @@ interface ConnectorLookupResult {
8222
8237
  **EventEnvelope** (interface)
8223
8238
  ```typescript
8224
8239
  interface EventEnvelope {
8240
+ specVersion: number
8225
8241
  id: string
8226
8242
  type: string
8227
8243
  entityType: string | null
@@ -8234,6 +8250,8 @@ interface EventEnvelope {
8234
8250
  contactId: string | null
8235
8251
  userId: string | null
8236
8252
  externalIds: Record<string, string>
8253
+ anonymousId: string | null
8254
+ sessionId: string | null
8237
8255
  emailHash?: string | null
8238
8256
  phoneHash?: string | null
8239
8257
  hashAlgorithm?: 'hmac-sha256' | null
@@ -8243,6 +8261,7 @@ interface EventEnvelope {
8243
8261
  identityPolicy: IdentityPolicy
8244
8262
  product: { id: string; name?: string | null; sku?: string | null; gtin?: string | null; variantId?: string | null; batchId?: string | null } | null
8245
8263
  proof: { id: string } | null
8264
+ consent: { channels: Record<'email' | 'sms' | 'push' | 'whatsapp', 'opted_in' | 'opted_out' | 'suppressed' | 'unknown'>; topics: Record<string, boolean>; updatedAt: string | null } | null
8246
8265
  subject: Record<string, unknown>
8247
8266
  event?: { name: string; attributes: Record<string, unknown>; rule: string | null }
8248
8267
  attributes: Record<string, unknown>
@@ -12984,6 +13003,22 @@ Save a connector's non-secret Connect-form values and/or settings. PUT /integrat
12984
13003
  input: Record<string, unknown> & { flowId?: string } = {}) → `Promise<T>`
12985
13004
  Run a connector step from the console: `testConnection` → ConnectorTestResult, `describeTarget` → ConnectorTargetSchema, `sample` → ConnectorRecords, and dry runs of `shape` ({ records }) → ConnectorShapeResult and `serialize` ({ documents }) → ConnectorSerializeResult. The server adds the installation; pass `flowId` to layer that flow's settings. POST /integrations/connectors/:appId/steps/:step
12986
13005
 
13006
+ **runConnectorAction**(collectionId: string,
13007
+ appId: string,
13008
+ action: string,
13009
+ input: Record<string, unknown> = {}) → `Promise<T>`
13010
+ Invoke a connector ACTION from the console (any visibility): redeem, enrol, adjust points, issue a coupon… The connector's function runs with its own credentials and is told who asked. POST /integrations/connectors/:appId/actions/:action { input }
13011
+
13012
+ **listConnectorActions**(collectionId: string,
13013
+ appId: string) → `Promise<`
13014
+ PUBLIC (the Hub, a site): the connector actions the caller may invoke — those declared `public`, plus `member` ones when signed in. GET /public/collection/:collectionId/connectors/:appId/actions
13015
+
13016
+ **callConnectorAction**(collectionId: string,
13017
+ appId: string,
13018
+ action: string,
13019
+ input: Record<string, unknown> = {}) → `Promise<T>`
13020
+ PUBLIC (the Hub, a site): invoke a connector action — e.g. redeem a reward for the signed-in member. 401 SIGN_IN_REQUIRED for a `member` action without a session; 403 for an admin-only one; 400 with `missing[]` when a required input is absent. POST /public/collection/:collectionId/connectors/:appId/actions/:action { input }
13021
+
12987
13022
  ### interactions
12988
13023
 
12989
13024
  **query**(collectionId: string,
@@ -92,6 +92,7 @@ Types: `AppConnectorDeclaration` and friends in `@proveanything/smartlinks` (`sr
92
92
  | `aiGuide` | A guide the mapping assistant reads: the provider's quirks, required fields, good defaults. |
93
93
  | `recordId` | Path of the provider's own id in a fetched record (e.g. `gtin`). Inbound records are landed and matched by it, so a re-import updates the same product instead of creating another. Default `id`; a flow can override it (`config.upsert.externalIdPath`). |
94
94
  | `limits` | `{ requestsPerSecond }` toward the provider, enforced across every SmartLinks worker; `{ maxEvents, maxAttributes }` when the provider caps registered events — a flow's `eventMapping` is checked against them when saved. |
95
+ | `actions[]` | Provider operations invoked on demand (redeem, enrol, issue…): name, title, function, `visibility`, input schema. See [Actions](#actions-provider-operations-on-demand). |
95
96
 
96
97
  ---
97
98
 
@@ -219,6 +220,61 @@ A connector should ship an `eventMapping` **preset** with the provider's standar
219
220
 
220
221
  ---
221
222
 
223
+ ## Actions: provider operations on demand
224
+
225
+ Steps are called by the engine. **Actions** are called when someone asks: redeem a reward, enrol a
226
+ member, issue a coupon, raise a ticket, send a receipt, check a balance. A connector declares them in
227
+ the manifest and implements each as a server function; the platform decides who may call them and
228
+ runs them with the connector's credentials, telling the function who asked.
229
+
230
+ ```jsonc
231
+ "connector": {
232
+ "actions": [
233
+ { "name": "redeem", "title": "Redeem a reward", "function": "redeemReward", "visibility": "member",
234
+ "input": { "type": "object", "required": ["rewardId"], "properties": { "rewardId": { "type": "string" } } } },
235
+ { "name": "enrol", "title": "Join the programme", "function": "enrolMember", "visibility": "member" },
236
+ { "name": "coupon-status", "title": "Coupon status", "function": "couponStatus", "visibility": "public" },
237
+ { "name": "adjust-points", "title": "Adjust points", "function": "adjustPoints" } // admin only (the default)
238
+ ]
239
+ }
240
+ ```
241
+
242
+ | `visibility` | Who may invoke it |
243
+ |---|---|
244
+ | `admin` (default) | the console and admin API; any app function |
245
+ | `member` | also a signed-in person through the Hub / a site |
246
+ | `public` | also anyone |
247
+
248
+ The function receives `event.body = { input, installation, caller: { userId, contactId, role, surface } }`
249
+ and returns JSON (or a `Response`). `input.required` fields from the declared schema are checked before
250
+ the function runs (400 with `missing[]`); fuller validation is the connector's job.
251
+
252
+ ```ts
253
+ export async function redeemReward(ctx: any, event: any) {
254
+ const { input, caller } = event.body
255
+ if (!caller.contactId) return new Response(JSON.stringify({ error: 'no_contact' }), { status: 400 })
256
+ const memberId = (await ctx.sl.contacts.forCaller())?.customFields?.loyaltyMemberId // written by Profile
257
+ const res = await ctx.fetch(`${api}/members/${memberId}/redemptions`, { method: 'POST', headers: auth(await ctx.secrets.get('apiKey')), body: JSON.stringify({ reward: input.rewardId }) })
258
+ return res.json()
259
+ }
260
+ ```
261
+
262
+ Calling an action:
263
+
264
+ ```ts
265
+ // From the Hub or a site (visibility public / member):
266
+ const { actions } = await integrations.listConnectorActions(collectionId, 'acme-loyalty')
267
+ const out = await integrations.callConnectorAction(collectionId, 'acme-loyalty', 'redeem', { rewardId: 'R-5' })
268
+
269
+ // From another app's server function (any visibility; needs sl:connectors:write):
270
+ const out = await ctx.sl.connectors.call('acme-loyalty', 'redeem', { rewardId: 'R-5' })
271
+
272
+ // From the console (any visibility):
273
+ await integrations.runConnectorAction(collectionId, 'acme-loyalty', 'adjust-points', { memberId, delta: 100 })
274
+ ```
275
+
276
+ ---
277
+
222
278
  ## Where configuration lives
223
279
 
224
280
  | What | Where |
@@ -118,6 +118,7 @@ mapped with the flow's field mappings (or handed to a connector's `serialize`):
118
118
 
119
119
  ```jsonc
120
120
  {
121
+ "specVersion": 1, // bumped only when a consumer could break
121
122
  "id": "0b2b…", // the event's id — stable across retries; use it as the destination's idempotency / transaction id
122
123
  "type": "interaction.submitted:quiz-1",
123
124
  "entityType": "interaction", "entityId": "0b2b…",
@@ -126,12 +127,14 @@ mapped with the flow's field mappings (or handed to a connector's `serialize`):
126
127
  "identity": {
127
128
  "contactId": "…", "userId": "…",
128
129
  "externalIds": { "acme-loyalty": "M-9", "crm": "C-1" }, // ids other systems know this person by
130
+ "anonymousId": "v-77", "sessionId": "s-1", // the visitor / device and session, before anyone signed in (every policy)
129
131
  "emailHash": "…", "phoneHash": "…", "hashAlgorithm": "hmac-sha256" // policy 'hashed' (default)
130
132
  // "email", "phone": only under policy 'raw'
131
133
  },
132
134
  "identityPolicy": "hashed",
133
135
  "product": { "id": "P1", "name": "Widget", "sku": "W-1", "gtin": "0500…", "variantId": null, "batchId": null },
134
136
  "proof": { "id": "…" },
137
+ "consent": { "channels": { "email": "opted_in", "sms": "suppressed", "push": "unknown", "whatsapp": "unknown" }, "topics": { "offers": true }, "updatedAt": "…" }, // null when about nobody we hold
135
138
  "subject": { "interactionId": "quiz-1", "outcome": "completed", "metadata": { "score": 9 }, "source": "hub" },
136
139
  "event": { "name": "engagement_completed", "attributes": { "kind": "quiz", "score": 9 }, "rule": "quiz" }, // from eventMapping
137
140
  "attributes": {}
@@ -142,6 +145,11 @@ Field mappings read envelope paths (`identity.externalIds.acme-loyalty`, `subjec
142
145
  `event.name`, `product.sku`). With no mappings the envelope itself is sent. A failed event delivery
143
146
  keeps its envelope and is retried from it; a product is re-read instead.
144
147
 
148
+ **CloudEvents.** `config.envelopeFormat: 'cloudevents'` wraps each delivered body (mapped or not) as a
149
+ CloudEvents 1.0 structured event — `{ specversion: '1.0', id, type, source: 'urn:smartlinks:collection:<id>',
150
+ subject, time, datacontenttype, dataschema: 'urn:smartlinks:envelope:v1', data }` — the shape every event
151
+ bus, lake and serverless tool already consumes.
152
+
145
153
  ```ts
146
154
  const flow = await integrations.createFlow(collectionId, {
147
155
  appId: 'core-integrations', direction: 'outbound', name: 'Engagement → loyalty', status: 'active',
@@ -518,6 +518,7 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
518
518
  | Put a visitor's details on a contact | `ctx.sl.contacts.upsert({ email, firstName, … })` → `{ contactId, created }` (fills empty fields only) | `sl:contacts:write` |
519
519
  | Record "they did X" | `ctx.sl.interactions.record({ interactionId, contactId, outcome, metadata })` | `sl:interactions:write` |
520
520
  | Land a loyalty / CRM provider's view of a person (member id, tier, points, tags, external ids) | `ctx.sl.members.apply({ identity, contact?, loyalty?, tags?, externalIds? }, { provider })` → `{ contactId, op }` — matches by id, external id, email or phone; profile fields fill-empty only; see [connectors.md](connectors.md#loyalty-and-crm-connectors) | `sl:members:write` |
521
+ | Invoke a connector's action — redeem, enrol, issue a coupon, raise a ticket — with the connector's credentials | `ctx.sl.connectors.call('acme-loyalty', 'redeem', { rewardId })` → the action's result; `ctx.sl.connectors.actions(appId)` lists them; see [connectors.md](connectors.md#actions-provider-operations-on-demand) | `sl:connectors:write` |
521
522
  | Ask "is this person gold or above?" without holding their member record | `ctx.sl.entitlements.check('tier>=gold')` → `{ allowed, basis, asOf, attributes }` for the signed-in caller (or `{ identity, token, fallback }`); see [entitlements.md](entitlements.md) | `sl:entitlements:read` |
522
523
  | Take a booking with your own checks (hours, queue token, payment) | `ctx.sl.bookings.create({ slotId \| resource + time, contactId, status, idempotencyKey })` — never oversold, never double-booked; also `confirm` / `cancel` / `move` / `mine` / `freebusy` / `message` (everyone booked, hydrated) / `slots.*` / `blocks.*` (see [bookings.md](bookings.md)) | `sl:bookings:write` (`sl:bookings:read` to read) |
523
524
 
package/dist/openapi.yaml CHANGED
@@ -21235,6 +21235,32 @@ components:
21235
21235
  - id
21236
21236
  - title
21237
21237
  - component
21238
+ ConnectorAction:
21239
+ type: object
21240
+ properties:
21241
+ name:
21242
+ type: string
21243
+ title:
21244
+ type: string
21245
+ description:
21246
+ type: string
21247
+ function:
21248
+ type: string
21249
+ visibility:
21250
+ type: string
21251
+ enum:
21252
+ - admin
21253
+ - member
21254
+ - public
21255
+ input:
21256
+ type: object
21257
+ additionalProperties: true
21258
+ output:
21259
+ type: object
21260
+ additionalProperties: true
21261
+ required:
21262
+ - name
21263
+ - function
21238
21264
  AppConnectorDeclaration:
21239
21265
  type: object
21240
21266
  properties:
@@ -21288,6 +21314,10 @@ components:
21288
21314
  type: array
21289
21315
  items:
21290
21316
  $ref: "#/components/schemas/ConnectorPanel"
21317
+ actions:
21318
+ type: array
21319
+ items:
21320
+ $ref: "#/components/schemas/ConnectorAction"
21291
21321
  aiGuide:
21292
21322
  type: string
21293
21323
  recordId:
@@ -29060,6 +29090,10 @@ components:
29060
29090
  additionalProperties: true
29061
29091
  eventMapping:
29062
29092
  $ref: "#/components/schemas/EventMapping"
29093
+ envelopeFormat:
29094
+ type: string
29095
+ enum:
29096
+ - cloudevents
29063
29097
  connection:
29064
29098
  $ref: "#/components/schemas/FlowConnection"
29065
29099
  fieldMappings:
@@ -29520,6 +29554,8 @@ components:
29520
29554
  EventEnvelope:
29521
29555
  type: object
29522
29556
  properties:
29557
+ specVersion:
29558
+ type: number
29523
29559
  id:
29524
29560
  type: string
29525
29561
  type:
@@ -29547,6 +29583,10 @@ components:
29547
29583
  type: object
29548
29584
  additionalProperties:
29549
29585
  type: string
29586
+ anonymousId:
29587
+ type: string
29588
+ sessionId:
29589
+ type: string
29550
29590
  emailHash:
29551
29591
  type: string
29552
29592
  phoneHash:
@@ -29567,6 +29607,9 @@ components:
29567
29607
  proof:
29568
29608
  type: object
29569
29609
  additionalProperties: true
29610
+ consent:
29611
+ type: object
29612
+ additionalProperties: true
29570
29613
  subject:
29571
29614
  type: object
29572
29615
  additionalProperties: true
@@ -29577,6 +29620,7 @@ components:
29577
29620
  type: object
29578
29621
  additionalProperties: true
29579
29622
  required:
29623
+ - specVersion
29580
29624
  - id
29581
29625
  - type
29582
29626
  - entityType
@@ -29589,9 +29633,12 @@ components:
29589
29633
  - contactId
29590
29634
  - userId
29591
29635
  - externalIds
29636
+ - anonymousId
29637
+ - sessionId
29592
29638
  - identityPolicy
29593
29639
  - product
29594
29640
  - proof
29641
+ - consent
29595
29642
  - subject
29596
29643
  - attributes
29597
29644
  CapturedRequest:
@@ -1030,6 +1030,27 @@ export interface ConnectorPanel {
1030
1030
  /** Exported component name in the app's widgets bundle (manifest `widgets.files`). */
1031
1031
  component: string;
1032
1032
  }
1033
+ /**
1034
+ * A named provider operation invoked ON DEMAND (not by the engine): redeem a reward, enrol a member,
1035
+ * issue a coupon, raise a ticket, send a receipt. Called by an app function (`ctx.sl.connectors.call`),
1036
+ * by the Hub (public route — only `public`, or `member` for a signed-in person) or by the console. The
1037
+ * function receives `{ input, installation, caller }` and returns JSON; it runs with the connector's
1038
+ * own credentials and is told who asked.
1039
+ */
1040
+ export interface ConnectorAction {
1041
+ /** `^[a-zA-Z][a-zA-Z0-9_-]{0,63}$` */
1042
+ name: string;
1043
+ title?: string;
1044
+ description?: string;
1045
+ /** This app's server function implementing it. */
1046
+ function: string;
1047
+ /** Who may invoke it outside functions and the console. Default 'admin'. */
1048
+ visibility?: 'admin' | 'member' | 'public';
1049
+ /** JSON Schema for `input`; `required` fields are checked before the function runs. */
1050
+ input?: Record<string, unknown>;
1051
+ /** JSON Schema for the result (documentation; not enforced). */
1052
+ output?: Record<string, unknown>;
1053
+ }
1033
1054
  /**
1034
1055
  * The connector contract. Every function named here is one of this app's server functions
1035
1056
  * (manifest `functions.definitions`); the engine calls it with collection authority, capped by the
@@ -1092,6 +1113,8 @@ export interface AppConnectorDeclaration {
1092
1113
  lookup?: string;
1093
1114
  };
1094
1115
  panels?: ConnectorPanel[];
1116
+ /** On-demand operations the connector offers (see ConnectorAction). */
1117
+ actions?: ConnectorAction[];
1095
1118
  /** Path (under the app's public files) of a guide the AI mapping assistant reads. */
1096
1119
  aiGuide?: string;
1097
1120
  /**
@@ -92,6 +92,12 @@ export interface IntegrationFlowConfig {
92
92
  };
93
93
  /** Event-sourced flows: fold SmartLinks events into the provider's registered events. */
94
94
  eventMapping?: EventMapping;
95
+ /**
96
+ * Wrap each delivered body as a CloudEvents 1.0 structured event
97
+ * ({ specversion, id, type, source, subject, time, datacontenttype, dataschema, data }) — the shape
98
+ * event buses, lakes and serverless tools consume without custom code.
99
+ */
100
+ envelopeFormat?: 'cloudevents';
95
101
  connection?: FlowConnection;
96
102
  fieldMappings?: FieldMapping[];
97
103
  /** Run this flow through a connector app's functions instead of the built-in mapper/transport. */
@@ -369,6 +375,8 @@ export interface ConnectorLookupResult {
369
375
  * connector's `serialize` / `send`, after the flow's identity policy and event mapping.
370
376
  */
371
377
  export interface EventEnvelope {
378
+ /** Schema version of this envelope. Bumped only when a consumer could break (a field removed or changed). */
379
+ specVersion: number;
372
380
  /** The event's id: stable across retries; use it as the provider's transaction / idempotency id. */
373
381
  id: string;
374
382
  /** Spine event name, e.g. 'interaction.submitted:<typeId>', 'contact.updated', 'tag.scanned:scan_redirect'. */
@@ -384,6 +392,9 @@ export interface EventEnvelope {
384
392
  userId: string | null;
385
393
  /** Ids other systems know this person by, keyed by system (a connector's `provider`). */
386
394
  externalIds: Record<string, string>;
395
+ /** The visitor / device id before anyone signed in (an analytics destination stitches on it later). Under every policy. */
396
+ anonymousId: string | null;
397
+ sessionId: string | null;
387
398
  emailHash?: string | null;
388
399
  phoneHash?: string | null;
389
400
  hashAlgorithm?: 'hmac-sha256' | null;
@@ -403,6 +414,15 @@ export interface EventEnvelope {
403
414
  proof: {
404
415
  id: string;
405
416
  } | null;
417
+ /**
418
+ * The person's communication consent, summarised per channel from the contact's comms state; null
419
+ * when the event is about nobody we hold. A CRM or CDP must not act against it.
420
+ */
421
+ consent: {
422
+ channels: Record<'email' | 'sms' | 'push' | 'whatsapp', 'opted_in' | 'opted_out' | 'suppressed' | 'unknown'>;
423
+ topics: Record<string, boolean>;
424
+ updatedAt: string | null;
425
+ } | null;
406
426
  /** The event's own fields: an interaction's interactionId / outcome / metadata, a scan's codeId / country, a contact's profile. */
407
427
  subject: Record<string, unknown>;
408
428
  /** From the flow's event mapping, when it has one. */
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.1.14 | Generated: 2026-11-09T13:01:05.900Z
3
+ Version: 2.1.15 | Generated: 2026-11-09T15:17:42.472Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -65,7 +65,7 @@ For detailed guides on specific features:
65
65
  - **[Analytics Metadata Conventions](analytics-metadata-conventions.md)** - Standard recommended keys and conventions for analytics metadata fields
66
66
  - **[Loyalty: Points, Members & Earning Rules](loyalty.md)** - Loyalty schemes, automatic point earning via interaction rules, member balances, transaction history, and manual adjustments
67
67
  - **[Integrations](integrations.md)** - Inbound/outbound integration flows + the sealed-secret store; triggers (manual/event/schedule), event patterns, the event envelope (interaction/contact/event sources), identity policy, event mapping, signed deliveries, contact as a target, field mappings
68
- - **[Connectors](connectors.md)** - Connector apps (adapters for one external provider): a manifest `connector` block + server functions (testConnection/describeTarget/sample/fetch/shape/serialize/send/describeEvents/lookup); loyalty and CRM connectors; core does the mapping
68
+ - **[Connectors](connectors.md)** - Connector apps (adapters for one external provider): a manifest `connector` block + server functions (testConnection/describeTarget/sample/fetch/shape/serialize/send/describeEvents/lookup); loyalty and CRM connectors; actions (provider operations on demand); core does the mapping
69
69
  - **[Entitlements](entitlements.md)** - "Is this person gold or above?" answered on demand from signed claims, a connector lookup (cached), or the contact — never a mirrored member base; gates on interaction types and in server functions
70
70
  - **[AI Tools & Skills](ai-tools-and-skills.md)** - The AI capability catalog: skills apps invoke by name (e.g. research.brand), the tools the AI reaches for (web fetch/screenshot/brand assets/image gen), the agent loop, and how apps discover them
71
71
  - **[Deep Link Discovery](deep-link-discovery.md)** - Registering and discovering navigable app states for portal menus and AI orchestration
@@ -3112,6 +3112,19 @@ interface ConnectorPanel {
3112
3112
  }
3113
3113
  ```
3114
3114
 
3115
+ **ConnectorAction** (interface)
3116
+ ```typescript
3117
+ interface ConnectorAction {
3118
+ name: string
3119
+ title?: string
3120
+ description?: string
3121
+ function: string
3122
+ visibility?: 'admin' | 'member' | 'public'
3123
+ input?: Record<string, unknown>
3124
+ output?: Record<string, unknown>
3125
+ }
3126
+ ```
3127
+
3115
3128
  **AppConnectorDeclaration** (interface)
3116
3129
  ```typescript
3117
3130
  interface AppConnectorDeclaration {
@@ -3134,6 +3147,7 @@ interface AppConnectorDeclaration {
3134
3147
  lookup?: string
3135
3148
  }
3136
3149
  panels?: ConnectorPanel[]
3150
+ actions?: ConnectorAction[]
3137
3151
  aiGuide?: string
3138
3152
  recordId?: string
3139
3153
  limits?: { requestsPerSecond?: number; maxEvents?: number; maxAttributes?: number }
@@ -7980,6 +7994,7 @@ interface IntegrationFlowConfig {
7980
7994
  signing?: { credentialRef: string; previousCredentialRef?: string }
7981
7995
  identity?: { policy?: IdentityPolicy; hashKeyCredentialRef?: string }
7982
7996
  eventMapping?: EventMapping
7997
+ envelopeFormat?: 'cloudevents'
7983
7998
  connection?: FlowConnection
7984
7999
  fieldMappings?: FieldMapping[]
7985
8000
  connector?: FlowConnectorRef
@@ -8222,6 +8237,7 @@ interface ConnectorLookupResult {
8222
8237
  **EventEnvelope** (interface)
8223
8238
  ```typescript
8224
8239
  interface EventEnvelope {
8240
+ specVersion: number
8225
8241
  id: string
8226
8242
  type: string
8227
8243
  entityType: string | null
@@ -8234,6 +8250,8 @@ interface EventEnvelope {
8234
8250
  contactId: string | null
8235
8251
  userId: string | null
8236
8252
  externalIds: Record<string, string>
8253
+ anonymousId: string | null
8254
+ sessionId: string | null
8237
8255
  emailHash?: string | null
8238
8256
  phoneHash?: string | null
8239
8257
  hashAlgorithm?: 'hmac-sha256' | null
@@ -8243,6 +8261,7 @@ interface EventEnvelope {
8243
8261
  identityPolicy: IdentityPolicy
8244
8262
  product: { id: string; name?: string | null; sku?: string | null; gtin?: string | null; variantId?: string | null; batchId?: string | null } | null
8245
8263
  proof: { id: string } | null
8264
+ consent: { channels: Record<'email' | 'sms' | 'push' | 'whatsapp', 'opted_in' | 'opted_out' | 'suppressed' | 'unknown'>; topics: Record<string, boolean>; updatedAt: string | null } | null
8246
8265
  subject: Record<string, unknown>
8247
8266
  event?: { name: string; attributes: Record<string, unknown>; rule: string | null }
8248
8267
  attributes: Record<string, unknown>
@@ -12984,6 +13003,22 @@ Save a connector's non-secret Connect-form values and/or settings. PUT /integrat
12984
13003
  input: Record<string, unknown> & { flowId?: string } = {}) → `Promise<T>`
12985
13004
  Run a connector step from the console: `testConnection` → ConnectorTestResult, `describeTarget` → ConnectorTargetSchema, `sample` → ConnectorRecords, and dry runs of `shape` ({ records }) → ConnectorShapeResult and `serialize` ({ documents }) → ConnectorSerializeResult. The server adds the installation; pass `flowId` to layer that flow's settings. POST /integrations/connectors/:appId/steps/:step
12986
13005
 
13006
+ **runConnectorAction**(collectionId: string,
13007
+ appId: string,
13008
+ action: string,
13009
+ input: Record<string, unknown> = {}) → `Promise<T>`
13010
+ Invoke a connector ACTION from the console (any visibility): redeem, enrol, adjust points, issue a coupon… The connector's function runs with its own credentials and is told who asked. POST /integrations/connectors/:appId/actions/:action { input }
13011
+
13012
+ **listConnectorActions**(collectionId: string,
13013
+ appId: string) → `Promise<`
13014
+ PUBLIC (the Hub, a site): the connector actions the caller may invoke — those declared `public`, plus `member` ones when signed in. GET /public/collection/:collectionId/connectors/:appId/actions
13015
+
13016
+ **callConnectorAction**(collectionId: string,
13017
+ appId: string,
13018
+ action: string,
13019
+ input: Record<string, unknown> = {}) → `Promise<T>`
13020
+ PUBLIC (the Hub, a site): invoke a connector action — e.g. redeem a reward for the signed-in member. 401 SIGN_IN_REQUIRED for a `member` action without a session; 403 for an admin-only one; 400 with `missing[]` when a required input is absent. POST /public/collection/:collectionId/connectors/:appId/actions/:action { input }
13021
+
12987
13022
  ### interactions
12988
13023
 
12989
13024
  **query**(collectionId: string,
@@ -92,6 +92,7 @@ Types: `AppConnectorDeclaration` and friends in `@proveanything/smartlinks` (`sr
92
92
  | `aiGuide` | A guide the mapping assistant reads: the provider's quirks, required fields, good defaults. |
93
93
  | `recordId` | Path of the provider's own id in a fetched record (e.g. `gtin`). Inbound records are landed and matched by it, so a re-import updates the same product instead of creating another. Default `id`; a flow can override it (`config.upsert.externalIdPath`). |
94
94
  | `limits` | `{ requestsPerSecond }` toward the provider, enforced across every SmartLinks worker; `{ maxEvents, maxAttributes }` when the provider caps registered events — a flow's `eventMapping` is checked against them when saved. |
95
+ | `actions[]` | Provider operations invoked on demand (redeem, enrol, issue…): name, title, function, `visibility`, input schema. See [Actions](#actions-provider-operations-on-demand). |
95
96
 
96
97
  ---
97
98
 
@@ -219,6 +220,61 @@ A connector should ship an `eventMapping` **preset** with the provider's standar
219
220
 
220
221
  ---
221
222
 
223
+ ## Actions: provider operations on demand
224
+
225
+ Steps are called by the engine. **Actions** are called when someone asks: redeem a reward, enrol a
226
+ member, issue a coupon, raise a ticket, send a receipt, check a balance. A connector declares them in
227
+ the manifest and implements each as a server function; the platform decides who may call them and
228
+ runs them with the connector's credentials, telling the function who asked.
229
+
230
+ ```jsonc
231
+ "connector": {
232
+ "actions": [
233
+ { "name": "redeem", "title": "Redeem a reward", "function": "redeemReward", "visibility": "member",
234
+ "input": { "type": "object", "required": ["rewardId"], "properties": { "rewardId": { "type": "string" } } } },
235
+ { "name": "enrol", "title": "Join the programme", "function": "enrolMember", "visibility": "member" },
236
+ { "name": "coupon-status", "title": "Coupon status", "function": "couponStatus", "visibility": "public" },
237
+ { "name": "adjust-points", "title": "Adjust points", "function": "adjustPoints" } // admin only (the default)
238
+ ]
239
+ }
240
+ ```
241
+
242
+ | `visibility` | Who may invoke it |
243
+ |---|---|
244
+ | `admin` (default) | the console and admin API; any app function |
245
+ | `member` | also a signed-in person through the Hub / a site |
246
+ | `public` | also anyone |
247
+
248
+ The function receives `event.body = { input, installation, caller: { userId, contactId, role, surface } }`
249
+ and returns JSON (or a `Response`). `input.required` fields from the declared schema are checked before
250
+ the function runs (400 with `missing[]`); fuller validation is the connector's job.
251
+
252
+ ```ts
253
+ export async function redeemReward(ctx: any, event: any) {
254
+ const { input, caller } = event.body
255
+ if (!caller.contactId) return new Response(JSON.stringify({ error: 'no_contact' }), { status: 400 })
256
+ const memberId = (await ctx.sl.contacts.forCaller())?.customFields?.loyaltyMemberId // written by Profile
257
+ const res = await ctx.fetch(`${api}/members/${memberId}/redemptions`, { method: 'POST', headers: auth(await ctx.secrets.get('apiKey')), body: JSON.stringify({ reward: input.rewardId }) })
258
+ return res.json()
259
+ }
260
+ ```
261
+
262
+ Calling an action:
263
+
264
+ ```ts
265
+ // From the Hub or a site (visibility public / member):
266
+ const { actions } = await integrations.listConnectorActions(collectionId, 'acme-loyalty')
267
+ const out = await integrations.callConnectorAction(collectionId, 'acme-loyalty', 'redeem', { rewardId: 'R-5' })
268
+
269
+ // From another app's server function (any visibility; needs sl:connectors:write):
270
+ const out = await ctx.sl.connectors.call('acme-loyalty', 'redeem', { rewardId: 'R-5' })
271
+
272
+ // From the console (any visibility):
273
+ await integrations.runConnectorAction(collectionId, 'acme-loyalty', 'adjust-points', { memberId, delta: 100 })
274
+ ```
275
+
276
+ ---
277
+
222
278
  ## Where configuration lives
223
279
 
224
280
  | What | Where |
@@ -118,6 +118,7 @@ mapped with the flow's field mappings (or handed to a connector's `serialize`):
118
118
 
119
119
  ```jsonc
120
120
  {
121
+ "specVersion": 1, // bumped only when a consumer could break
121
122
  "id": "0b2b…", // the event's id — stable across retries; use it as the destination's idempotency / transaction id
122
123
  "type": "interaction.submitted:quiz-1",
123
124
  "entityType": "interaction", "entityId": "0b2b…",
@@ -126,12 +127,14 @@ mapped with the flow's field mappings (or handed to a connector's `serialize`):
126
127
  "identity": {
127
128
  "contactId": "…", "userId": "…",
128
129
  "externalIds": { "acme-loyalty": "M-9", "crm": "C-1" }, // ids other systems know this person by
130
+ "anonymousId": "v-77", "sessionId": "s-1", // the visitor / device and session, before anyone signed in (every policy)
129
131
  "emailHash": "…", "phoneHash": "…", "hashAlgorithm": "hmac-sha256" // policy 'hashed' (default)
130
132
  // "email", "phone": only under policy 'raw'
131
133
  },
132
134
  "identityPolicy": "hashed",
133
135
  "product": { "id": "P1", "name": "Widget", "sku": "W-1", "gtin": "0500…", "variantId": null, "batchId": null },
134
136
  "proof": { "id": "…" },
137
+ "consent": { "channels": { "email": "opted_in", "sms": "suppressed", "push": "unknown", "whatsapp": "unknown" }, "topics": { "offers": true }, "updatedAt": "…" }, // null when about nobody we hold
135
138
  "subject": { "interactionId": "quiz-1", "outcome": "completed", "metadata": { "score": 9 }, "source": "hub" },
136
139
  "event": { "name": "engagement_completed", "attributes": { "kind": "quiz", "score": 9 }, "rule": "quiz" }, // from eventMapping
137
140
  "attributes": {}
@@ -142,6 +145,11 @@ Field mappings read envelope paths (`identity.externalIds.acme-loyalty`, `subjec
142
145
  `event.name`, `product.sku`). With no mappings the envelope itself is sent. A failed event delivery
143
146
  keeps its envelope and is retried from it; a product is re-read instead.
144
147
 
148
+ **CloudEvents.** `config.envelopeFormat: 'cloudevents'` wraps each delivered body (mapped or not) as a
149
+ CloudEvents 1.0 structured event — `{ specversion: '1.0', id, type, source: 'urn:smartlinks:collection:<id>',
150
+ subject, time, datacontenttype, dataschema: 'urn:smartlinks:envelope:v1', data }` — the shape every event
151
+ bus, lake and serverless tool already consumes.
152
+
145
153
  ```ts
146
154
  const flow = await integrations.createFlow(collectionId, {
147
155
  appId: 'core-integrations', direction: 'outbound', name: 'Engagement → loyalty', status: 'active',
@@ -518,6 +518,7 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
518
518
  | Put a visitor's details on a contact | `ctx.sl.contacts.upsert({ email, firstName, … })` → `{ contactId, created }` (fills empty fields only) | `sl:contacts:write` |
519
519
  | Record "they did X" | `ctx.sl.interactions.record({ interactionId, contactId, outcome, metadata })` | `sl:interactions:write` |
520
520
  | Land a loyalty / CRM provider's view of a person (member id, tier, points, tags, external ids) | `ctx.sl.members.apply({ identity, contact?, loyalty?, tags?, externalIds? }, { provider })` → `{ contactId, op }` — matches by id, external id, email or phone; profile fields fill-empty only; see [connectors.md](connectors.md#loyalty-and-crm-connectors) | `sl:members:write` |
521
+ | Invoke a connector's action — redeem, enrol, issue a coupon, raise a ticket — with the connector's credentials | `ctx.sl.connectors.call('acme-loyalty', 'redeem', { rewardId })` → the action's result; `ctx.sl.connectors.actions(appId)` lists them; see [connectors.md](connectors.md#actions-provider-operations-on-demand) | `sl:connectors:write` |
521
522
  | Ask "is this person gold or above?" without holding their member record | `ctx.sl.entitlements.check('tier>=gold')` → `{ allowed, basis, asOf, attributes }` for the signed-in caller (or `{ identity, token, fallback }`); see [entitlements.md](entitlements.md) | `sl:entitlements:read` |
522
523
  | Take a booking with your own checks (hours, queue token, payment) | `ctx.sl.bookings.create({ slotId \| resource + time, contactId, status, idempotencyKey })` — never oversold, never double-booked; also `confirm` / `cancel` / `move` / `mine` / `freebusy` / `message` (everyone booked, hydrated) / `slots.*` / `blocks.*` (see [bookings.md](bookings.md)) | `sl:bookings:write` (`sl:bookings:read` to read) |
523
524
 
package/openapi.yaml CHANGED
@@ -21235,6 +21235,32 @@ components:
21235
21235
  - id
21236
21236
  - title
21237
21237
  - component
21238
+ ConnectorAction:
21239
+ type: object
21240
+ properties:
21241
+ name:
21242
+ type: string
21243
+ title:
21244
+ type: string
21245
+ description:
21246
+ type: string
21247
+ function:
21248
+ type: string
21249
+ visibility:
21250
+ type: string
21251
+ enum:
21252
+ - admin
21253
+ - member
21254
+ - public
21255
+ input:
21256
+ type: object
21257
+ additionalProperties: true
21258
+ output:
21259
+ type: object
21260
+ additionalProperties: true
21261
+ required:
21262
+ - name
21263
+ - function
21238
21264
  AppConnectorDeclaration:
21239
21265
  type: object
21240
21266
  properties:
@@ -21288,6 +21314,10 @@ components:
21288
21314
  type: array
21289
21315
  items:
21290
21316
  $ref: "#/components/schemas/ConnectorPanel"
21317
+ actions:
21318
+ type: array
21319
+ items:
21320
+ $ref: "#/components/schemas/ConnectorAction"
21291
21321
  aiGuide:
21292
21322
  type: string
21293
21323
  recordId:
@@ -29060,6 +29090,10 @@ components:
29060
29090
  additionalProperties: true
29061
29091
  eventMapping:
29062
29092
  $ref: "#/components/schemas/EventMapping"
29093
+ envelopeFormat:
29094
+ type: string
29095
+ enum:
29096
+ - cloudevents
29063
29097
  connection:
29064
29098
  $ref: "#/components/schemas/FlowConnection"
29065
29099
  fieldMappings:
@@ -29520,6 +29554,8 @@ components:
29520
29554
  EventEnvelope:
29521
29555
  type: object
29522
29556
  properties:
29557
+ specVersion:
29558
+ type: number
29523
29559
  id:
29524
29560
  type: string
29525
29561
  type:
@@ -29547,6 +29583,10 @@ components:
29547
29583
  type: object
29548
29584
  additionalProperties:
29549
29585
  type: string
29586
+ anonymousId:
29587
+ type: string
29588
+ sessionId:
29589
+ type: string
29550
29590
  emailHash:
29551
29591
  type: string
29552
29592
  phoneHash:
@@ -29567,6 +29607,9 @@ components:
29567
29607
  proof:
29568
29608
  type: object
29569
29609
  additionalProperties: true
29610
+ consent:
29611
+ type: object
29612
+ additionalProperties: true
29570
29613
  subject:
29571
29614
  type: object
29572
29615
  additionalProperties: true
@@ -29577,6 +29620,7 @@ components:
29577
29620
  type: object
29578
29621
  additionalProperties: true
29579
29622
  required:
29623
+ - specVersion
29580
29624
  - id
29581
29625
  - type
29582
29626
  - entityType
@@ -29589,9 +29633,12 @@ components:
29589
29633
  - contactId
29590
29634
  - userId
29591
29635
  - externalIds
29636
+ - anonymousId
29637
+ - sessionId
29592
29638
  - identityPolicy
29593
29639
  - product
29594
29640
  - proof
29641
+ - consent
29595
29642
  - subject
29596
29643
  - attributes
29597
29644
  CapturedRequest:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.1.14",
3
+ "version": "2.1.15",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",