@proveanything/smartlinks 2.1.12 → 2.1.14

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.
@@ -302,6 +302,23 @@ A loyalty failure never causes the interaction event to fail — errors are caug
302
302
 
303
303
  ---
304
304
 
305
+ ## External loyalty platforms
306
+
307
+ The schemes above are SmartLinks' own points. When a brand runs its programme on an external loyalty or
308
+ engagement platform, the platform stays the record of truth and SmartLinks connects to it through a
309
+ **connector app** and the integration engine — no member base is mirrored here:
310
+
311
+ | Flow | How |
312
+ |---|---|
313
+ | **Earn** — what happens on a pack or in a Hub awards points there | an outbound flow with `sourceEntity: 'interaction' \| 'event'`, an `eventMapping` onto the provider's registered events, signed delivery, retries — [integrations.md](integrations.md#event-sourced-flows-and-the-envelope) |
314
+ | **Profile** — member id, tier and points on the contact | the connector's webhook function calls `ctx.sl.members.apply(...)`; the contact gets `customFields.loyalty` and flat `loyaltyTier / loyaltyPoints / loyaltyMemberId`, usable in segments and comms targeting — [connectors.md](connectors.md#loyalty-and-crm-connectors) |
315
+ | **Gate / display** — "Gold members only", live balance | the entitlement check, answered on demand from a signed claim, the connector's cached `lookup`, or the contact's fields — [entitlements.md](entitlements.md) |
316
+
317
+ A collection runs one loyalty source: the native schemes here, or an external provider through a
318
+ connector.
319
+
320
+ ---
321
+
305
322
  ## TypeScript Types
306
323
 
307
324
  ```typescript
@@ -517,6 +517,8 @@ Prefer the platform primitives above over a dependency; when you do need one, pi
517
517
  | Check a tapped tag is real | `ctx.sl.tags.resolve(proofId)` → `{ id, claimSetId, code }` or `null` | `sl:tags:read` |
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
+ | 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
+ | 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` |
520
522
  | 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) |
521
523
 
522
524
  Notes:
package/dist/openapi.yaml CHANGED
@@ -9641,6 +9641,38 @@ paths:
9641
9641
  description: Unauthorized
9642
9642
  404:
9643
9643
  description: Not found
9644
+ /authkit/{clientId}/auth/microsoft:
9645
+ post:
9646
+ tags:
9647
+ - authKit
9648
+ summary: authKit.microsoftLogin
9649
+ operationId: authKit_microsoftLogin
9650
+ security: []
9651
+ parameters:
9652
+ - name: clientId
9653
+ in: path
9654
+ required: true
9655
+ schema:
9656
+ type: string
9657
+ responses:
9658
+ 200:
9659
+ description: Success
9660
+ content:
9661
+ application/json:
9662
+ schema:
9663
+ $ref: "#/components/schemas/AuthLoginResponse"
9664
+ 400:
9665
+ description: Bad request
9666
+ 401:
9667
+ description: Unauthorized
9668
+ 404:
9669
+ description: Not found
9670
+ requestBody:
9671
+ required: true
9672
+ content:
9673
+ application/json:
9674
+ schema:
9675
+ $ref: "#/components/schemas/MicrosoftLoginOptions"
9644
9676
  /authkit/{clientId}/auth/phone/send-code:
9645
9677
  post:
9646
9678
  tags:
@@ -21248,6 +21280,10 @@ components:
21248
21280
  type: string
21249
21281
  fetch:
21250
21282
  type: string
21283
+ describeEvents:
21284
+ type: string
21285
+ lookup:
21286
+ type: string
21251
21287
  panels:
21252
21288
  type: array
21253
21289
  items:
@@ -23933,6 +23969,35 @@ components:
23933
23969
  additionalProperties: true
23934
23970
  required:
23935
23971
  - success
23972
+ MicrosoftLoginOptions:
23973
+ type: object
23974
+ properties:
23975
+ returnOrigin:
23976
+ type: string
23977
+ trustedDeviceToken:
23978
+ type: string
23979
+ AuthKitAccess:
23980
+ type: object
23981
+ properties:
23982
+ mode:
23983
+ type: string
23984
+ enum:
23985
+ - anyone
23986
+ - organisation
23987
+ domains:
23988
+ type: array
23989
+ items:
23990
+ type: string
23991
+ emails:
23992
+ type: array
23993
+ items:
23994
+ type: string
23995
+ microsoftTenants:
23996
+ type: object
23997
+ additionalProperties:
23998
+ type: string
23999
+ required:
24000
+ - mode
23936
24001
  AppleLoginOptions:
23937
24002
  type: object
23938
24003
  properties:
@@ -24449,6 +24514,10 @@ components:
24449
24514
  enum:
24450
24515
  - open
24451
24516
  - invite
24517
+ access:
24518
+ $ref: "#/components/schemas/AuthKitAccess"
24519
+ microsoftClientId:
24520
+ type: string
24452
24521
  required:
24453
24522
  - id
24454
24523
  AuthKitInviteRequest:
@@ -24581,6 +24650,7 @@ components:
24581
24650
  - ACCOUNT_EXISTS_UNVERIFIED
24582
24651
  - APPLE_AUTH_FAILED
24583
24652
  - SIGN_UP_CLOSED
24653
+ - NOT_IN_ORGANISATION
24584
24654
  MfaFactor:
24585
24655
  type: string
24586
24656
  enum:
@@ -28948,9 +29018,48 @@ components:
28948
29018
  additionalProperties: true
28949
29019
  required:
28950
29020
  - appId
29021
+ EventMappingRule:
29022
+ type: object
29023
+ properties:
29024
+ id:
29025
+ type: string
29026
+ match:
29027
+ type: object
29028
+ additionalProperties: true
29029
+ event:
29030
+ type: string
29031
+ attributes:
29032
+ type: object
29033
+ additionalProperties:
29034
+ type: object
29035
+ additionalProperties: true
29036
+ required:
29037
+ - event
29038
+ EventMapping:
29039
+ type: object
29040
+ properties:
29041
+ rules:
29042
+ type: array
29043
+ items:
29044
+ $ref: "#/components/schemas/EventMappingRule"
29045
+ unmatched:
29046
+ type: string
29047
+ enum:
29048
+ - skip
29049
+ - passthrough
29050
+ required:
29051
+ - rules
28951
29052
  IntegrationFlowConfig:
28952
29053
  type: object
28953
29054
  properties:
29055
+ signing:
29056
+ type: object
29057
+ additionalProperties: true
29058
+ identity:
29059
+ type: object
29060
+ additionalProperties: true
29061
+ eventMapping:
29062
+ $ref: "#/components/schemas/EventMapping"
28954
29063
  connection:
28955
29064
  $ref: "#/components/schemas/FlowConnection"
28956
29065
  fieldMappings:
@@ -28983,9 +29092,11 @@ components:
28983
29092
  schedule:
28984
29093
  type: string
28985
29094
  sourceEntity:
28986
- type: string
29095
+ type: object
29096
+ additionalProperties: true
28987
29097
  targetEntity:
28988
- type: string
29098
+ type: object
29099
+ additionalProperties: true
28989
29100
  config:
28990
29101
  $ref: "#/components/schemas/IntegrationFlowConfig"
28991
29102
  createdBy:
@@ -29358,6 +29469,131 @@ components:
29358
29469
  settings:
29359
29470
  type: object
29360
29471
  additionalProperties: true
29472
+ ConnectorEventsSchema:
29473
+ type: object
29474
+ properties:
29475
+ events:
29476
+ type: array
29477
+ items:
29478
+ type: object
29479
+ additionalProperties: true
29480
+ limits:
29481
+ type: object
29482
+ additionalProperties: true
29483
+ required:
29484
+ - events
29485
+ ConnectorLookupInput:
29486
+ type: object
29487
+ properties:
29488
+ installation:
29489
+ $ref: "#/components/schemas/ConnectorInstallation"
29490
+ identity:
29491
+ type: object
29492
+ additionalProperties: true
29493
+ want:
29494
+ type: array
29495
+ items:
29496
+ type: string
29497
+ required:
29498
+ - installation
29499
+ - identity
29500
+ ConnectorLookupResult:
29501
+ type: object
29502
+ properties:
29503
+ found:
29504
+ type: boolean
29505
+ memberId:
29506
+ type: string
29507
+ tier:
29508
+ type: string
29509
+ points:
29510
+ type: number
29511
+ status:
29512
+ type: string
29513
+ attributes:
29514
+ type: object
29515
+ additionalProperties: true
29516
+ asOf:
29517
+ type: string
29518
+ required:
29519
+ - found
29520
+ EventEnvelope:
29521
+ type: object
29522
+ properties:
29523
+ id:
29524
+ type: string
29525
+ type:
29526
+ type: string
29527
+ entityType:
29528
+ type: string
29529
+ entityId:
29530
+ type: string
29531
+ occurredAt:
29532
+ type: string
29533
+ collectionId:
29534
+ type: string
29535
+ orgId:
29536
+ type: string
29537
+ appId:
29538
+ type: string
29539
+ identity:
29540
+ type: object
29541
+ additionalProperties: true
29542
+ contactId:
29543
+ type: string
29544
+ userId:
29545
+ type: string
29546
+ externalIds:
29547
+ type: object
29548
+ additionalProperties:
29549
+ type: string
29550
+ emailHash:
29551
+ type: string
29552
+ phoneHash:
29553
+ type: string
29554
+ hashAlgorithm:
29555
+ type: string
29556
+ enum:
29557
+ - hmac-sha256
29558
+ email:
29559
+ type: string
29560
+ phone:
29561
+ type: string
29562
+ identityPolicy:
29563
+ $ref: "#/components/schemas/IdentityPolicy"
29564
+ product:
29565
+ type: object
29566
+ additionalProperties: true
29567
+ proof:
29568
+ type: object
29569
+ additionalProperties: true
29570
+ subject:
29571
+ type: object
29572
+ additionalProperties: true
29573
+ event:
29574
+ type: object
29575
+ additionalProperties: true
29576
+ attributes:
29577
+ type: object
29578
+ additionalProperties: true
29579
+ required:
29580
+ - id
29581
+ - type
29582
+ - entityType
29583
+ - entityId
29584
+ - occurredAt
29585
+ - collectionId
29586
+ - orgId
29587
+ - appId
29588
+ - identity
29589
+ - contactId
29590
+ - userId
29591
+ - externalIds
29592
+ - identityPolicy
29593
+ - product
29594
+ - proof
29595
+ - subject
29596
+ - attributes
29361
29597
  CapturedRequest:
29362
29598
  type: object
29363
29599
  properties:
@@ -29697,6 +29933,24 @@ components:
29697
29933
  - webhook
29698
29934
  - oauth2
29699
29935
  - none
29936
+ IdentityPolicy:
29937
+ type: string
29938
+ enum:
29939
+ - externalIds
29940
+ - hashed
29941
+ - raw
29942
+ FlowSourceEntity:
29943
+ type: string
29944
+ enum:
29945
+ - product
29946
+ - interaction
29947
+ - contact
29948
+ - event
29949
+ FlowTargetEntity:
29950
+ type: string
29951
+ enum:
29952
+ - product
29953
+ - contact
29700
29954
  UpdateFlowInput:
29701
29955
  $ref: "#/components/schemas/CreateFlowInput"
29702
29956
  ConnectorConsoleStep:
@@ -29707,6 +29961,8 @@ components:
29707
29961
  - sample
29708
29962
  - shape
29709
29963
  - serialize
29964
+ - describeEvents
29965
+ - lookup
29710
29966
  RunTrigger:
29711
29967
  type: string
29712
29968
  enum:
@@ -1078,6 +1078,18 @@ export interface AppConnectorDeclaration {
1078
1078
  send?: string;
1079
1079
  /** (ConnectorFetchInput) → ConnectorRecords: inbound pull, one page per call. */
1080
1080
  fetch?: string;
1081
+ /**
1082
+ * Loyalty / engagement providers: → ConnectorEventsSchema, the event names (and attributes) the
1083
+ * provider has registered for this installation. A flow's eventMapping is validated against it when
1084
+ * saved, so a mapping that names an unregistered event or breaks a limit is refused with the reason.
1085
+ */
1086
+ describeEvents?: string;
1087
+ /**
1088
+ * (ConnectorLookupInput) → ConnectorLookupResult: one identity → member id, tier, points, status.
1089
+ * Called by the entitlement check (see docs/entitlements.md) through a cache keyed on an identity
1090
+ * hash, for the TTL the collection sets — one call per active person per TTL, never a sync.
1091
+ */
1092
+ lookup?: string;
1081
1093
  };
1082
1094
  panels?: ConnectorPanel[];
1083
1095
  /** Path (under the app's public files) of a guide the AI mapping assistant reads. */
@@ -1088,9 +1100,15 @@ export interface AppConnectorDeclaration {
1088
1100
  * Default 'id'; a flow can override it with `config.upsert.externalIdPath`.
1089
1101
  */
1090
1102
  recordId?: string;
1091
- /** Requests per second toward the provider, shared by every SmartLinks worker. */
1103
+ /**
1104
+ * Requests per second toward the provider, shared by every SmartLinks worker; and, for providers that
1105
+ * cap registered events, how many distinct events / attributes per event they accept (a flow's
1106
+ * eventMapping is checked against them when saved).
1107
+ */
1092
1108
  limits?: {
1093
1109
  requestsPerSecond?: number;
1110
+ maxEvents?: number;
1111
+ maxAttributes?: number;
1094
1112
  };
1095
1113
  }
1096
1114
  /**
@@ -98,6 +98,40 @@ export type RefreshErrorCode = 'MISSING_REFRESH_TOKEN' | 'INVALID_REFRESH_TOKEN'
98
98
  * Options for {@link authKit.appleLogin}. All fields are optional — only the
99
99
  * `identityToken` (passed as a positional argument) is required by the server.
100
100
  */
101
+ /** Options for {@link authKit.microsoftLogin}. */
102
+ export interface MicrosoftLoginOptions {
103
+ /**
104
+ * The site the session will be handed to, when signing in through a popup page on another address
105
+ * (the SmartLinks Microsoft sign-in page does this). The server refuses unless it's one of the login
106
+ * setup's own sites (`MICROSOFT_ORIGIN_NOT_ALLOWED`). Leave it out when signing in on the site itself.
107
+ */
108
+ returnOrigin?: string;
109
+ /** Skip the step-up MFA challenge on a recognised device. */
110
+ trustedDeviceToken?: string;
111
+ }
112
+ /**
113
+ * Who can sign in through a login setup, whatever the method (password, magic link, phone, WhatsApp,
114
+ * Google, Apple, LinkedIn, Microsoft):
115
+ * - `anyone` (also: no `access`) — anyone, as before;
116
+ * - `organisation` — people from the listed organisations: a VERIFIED email at one of `domains`, one of
117
+ * the listed `emails`, or signing in with Microsoft from the domain's own Microsoft organisation. A
118
+ * Microsoft email alone doesn't count (an organisation can give its users any address), so on save
119
+ * the server looks up each domain's Microsoft organisation itself — nobody types a tenant id.
120
+ *
121
+ * Checked every time a session is issued (tightening it shuts people out from their next sign-in);
122
+ * refused with `NOT_IN_ORGANISATION` (403). Registration refuses outside addresses up front and never
123
+ * signs an unverified address straight in. Accounts with no email (phone / WhatsApp only) can't show
124
+ * they're from an organisation. The public config returns only `{ mode, domains }`.
125
+ */
126
+ export interface AuthKitAccess {
127
+ mode: 'anyone' | 'organisation';
128
+ /** e.g. `['acme.com']` (no `@`; exact domains — list subsidiaries' domains too). Up to 50. */
129
+ domains?: string[];
130
+ /** Particular people allowed besides the organisations, e.g. a contractor. Up to 50. Never public. */
131
+ emails?: string[];
132
+ /** Server-filled on save: `{ 'acme.com': '<Microsoft tenant id>' }` for domains with a Microsoft organisation. Read-only. */
133
+ microsoftTenants?: Record<string, string>;
134
+ }
101
135
  export interface AppleLoginOptions {
102
136
  /** Apple authorization code. Accepted but ignored by the server for now (reserved for future server-side token exchange). */
103
137
  authorizationCode?: string;
@@ -128,7 +162,7 @@ export interface AppleLoginOptions {
128
162
  * `ACCOUNT_EXISTS_UNVERIFIED` case also carries `requiresEmailVerification: true`
129
163
  * in `SmartlinksApiError.details`.
130
164
  */
131
- export type AuthKitErrorCode = 'MISSING_APPLE_TOKEN' | 'APPLE_AUTH_NOT_CONFIGURED' | 'INVALID_APPLE_TOKEN' | 'ACCOUNT_EXISTS_UNVERIFIED' | 'APPLE_AUTH_FAILED' | 'SIGN_UP_CLOSED';
165
+ export type AuthKitErrorCode = 'MISSING_APPLE_TOKEN' | 'APPLE_AUTH_NOT_CONFIGURED' | 'INVALID_APPLE_TOKEN' | 'ACCOUNT_EXISTS_UNVERIFIED' | 'APPLE_AUTH_FAILED' | 'SIGN_UP_CLOSED' | 'NOT_IN_ORGANISATION' | 'MISSING_MICROSOFT_TOKEN' | 'MICROSOFT_AUTH_NOT_CONFIGURED' | 'INVALID_MICROSOFT_TOKEN' | 'MICROSOFT_ORIGIN_NOT_ALLOWED' | 'MICROSOFT_AUTH_FAILED';
132
166
  /** A second-step method: a code by email or text message, or from an authenticator app. */
133
167
  export type MfaFactor = 'email' | 'sms' | 'totp';
134
168
  /**
@@ -445,6 +479,16 @@ export interface AuthKitConfig {
445
479
  * Setting `invite` needs own logins first (`OWN_LOGINS_REQUIRED`, 409).
446
480
  */
447
481
  signUp?: 'open' | 'invite';
482
+ /** Who can sign in — see {@link AuthKitAccess}. Absent = anyone. */
483
+ access?: AuthKitAccess;
484
+ /**
485
+ * Sign in with Microsoft: add `'microsoft'` to `enabledProviders`. SmartLinks' own Microsoft app serves
486
+ * every organisation and personal Microsoft accounts, so nothing else is needed. Set this only to show
487
+ * your OWN name on Microsoft's sign-in screen: the Application (client) ID of your own multitenant app
488
+ * registration (redirect URI: https://smartlinks.app/apps/account/stable/microsoft-proxy.html). On the
489
+ * public config it's the effective id (yours, else SmartLinks').
490
+ */
491
+ microsoftClientId?: string;
448
492
  }
449
493
  /** Body of {@link authKit.invite}. */
450
494
  export interface AuthKitInviteRequest {
@@ -38,7 +38,60 @@ export interface FlowConnectorRef {
38
38
  /** Per-flow settings, layered over the installation's. */
39
39
  settings?: Record<string, unknown>;
40
40
  }
41
+ import type { ConnectorInstallation } from './appManifest.js';
42
+ /** What about a person an event-sourced flow lets leave. Default 'hashed'. */
43
+ export type IdentityPolicy = 'externalIds' | 'hashed' | 'raw';
44
+ /**
45
+ * Many SmartLinks events → the provider's few registered event names. Rules are tried in order; the
46
+ * first match names the provider event and renders its attributes (a literal, a Liquid template over
47
+ * the envelope, or `{ path, default }`). An unmatched event is skipped (recorded as such) unless
48
+ * `unmatched: 'passthrough'`. Validated against the connector's `describeEvents` when the flow is saved.
49
+ */
50
+ export interface EventMappingRule {
51
+ id?: string;
52
+ match?: {
53
+ types?: string[];
54
+ interactionIds?: string[];
55
+ outcomes?: string[];
56
+ appIds?: string[];
57
+ };
58
+ /** The provider's registered event name. */
59
+ event: string;
60
+ attributes?: Record<string, string | number | boolean | {
61
+ path: string;
62
+ default?: unknown;
63
+ } | {
64
+ value: unknown;
65
+ }>;
66
+ }
67
+ export interface EventMapping {
68
+ rules: EventMappingRule[];
69
+ unmatched?: 'skip' | 'passthrough';
70
+ }
71
+ /** What an outbound flow reads: the product that changed, or ONE spine event as the envelope. */
72
+ export type FlowSourceEntity = 'product' | 'interaction' | 'contact' | 'event';
73
+ /** What an inbound flow writes. */
74
+ export type FlowTargetEntity = 'product' | 'contact';
41
75
  export interface IntegrationFlowConfig {
76
+ /**
77
+ * Signed deliveries: X-SmartLinks-Signature `t=<unix>,v1=<hex HMAC-SHA256(secret, t + '.' + body)>`
78
+ * over the exact bytes sent. `previousCredentialRef` keeps the old secret signing during a rotation.
79
+ */
80
+ signing?: {
81
+ credentialRef: string;
82
+ previousCredentialRef?: string;
83
+ };
84
+ /**
85
+ * Event-sourced flows: what about the person leaves (default 'hashed': ids + HMAC hashes of email and
86
+ * phone, never the values). `hashKeyCredentialRef` names the brand's own hash key, so hashes match a
87
+ * mirror they built; otherwise a per-collection platform key is used.
88
+ */
89
+ identity?: {
90
+ policy?: IdentityPolicy;
91
+ hashKeyCredentialRef?: string;
92
+ };
93
+ /** Event-sourced flows: fold SmartLinks events into the provider's registered events. */
94
+ eventMapping?: EventMapping;
42
95
  connection?: FlowConnection;
43
96
  fieldMappings?: FieldMapping[];
44
97
  /** Run this flow through a connector app's functions instead of the built-in mapper/transport. */
@@ -57,8 +110,10 @@ export interface IntegrationFlow {
57
110
  eventTypes: string[];
58
111
  /** Cron/interval string for scheduled flows, or null. */
59
112
  schedule: string | null;
60
- sourceEntity: string | null;
61
- targetEntity: string | null;
113
+ /** Outbound: what the flow reads (product | interaction | contact | event). */
114
+ sourceEntity: FlowSourceEntity | string | null;
115
+ /** Inbound: what the flow writes (product | contact). */
116
+ targetEntity: FlowTargetEntity | string | null;
62
117
  config: IntegrationFlowConfig;
63
118
  createdBy: string | null;
64
119
  createdAt: string;
@@ -267,7 +322,97 @@ export interface SaveConnectorInstallationInput {
267
322
  settings?: Record<string, unknown>;
268
323
  }
269
324
  /** Steps the console may run directly (send and fetch only run inside a flow; shape/serialize as dry runs). */
270
- export type ConnectorConsoleStep = 'testConnection' | 'describeTarget' | 'sample' | 'shape' | 'serialize';
325
+ export type ConnectorConsoleStep = 'testConnection' | 'describeTarget' | 'sample' | 'shape' | 'serialize' | 'describeEvents' | 'lookup';
326
+ /** `describeEvents` → what the provider has registered for this installation (loyalty / engagement). */
327
+ export interface ConnectorEventsSchema {
328
+ events: Array<{
329
+ name: string;
330
+ title?: string;
331
+ attributes?: Array<{
332
+ name: string;
333
+ type?: string;
334
+ required?: boolean;
335
+ }>;
336
+ }>;
337
+ limits?: {
338
+ maxEvents?: number;
339
+ maxAttributes?: number;
340
+ };
341
+ }
342
+ /** `lookup` input: one identity, as the collection's lookup policy sends it (raw, or hashed). */
343
+ export interface ConnectorLookupInput {
344
+ installation: ConnectorInstallation;
345
+ identity: {
346
+ externalId?: string | null;
347
+ email?: string | null;
348
+ phone?: string | null;
349
+ emailHash?: string | null;
350
+ phoneHash?: string | null;
351
+ contactId?: string | null;
352
+ };
353
+ /** Attribute names the gate wants, e.g. ['tier', 'points', 'status']. */
354
+ want?: string[];
355
+ }
356
+ /** `lookup` result. Throw (or return non-2xx) for an outage — never `found: false`. */
357
+ export interface ConnectorLookupResult {
358
+ found: boolean;
359
+ memberId?: string | null;
360
+ tier?: string | null;
361
+ points?: number | null;
362
+ status?: string | null;
363
+ attributes?: Record<string, unknown>;
364
+ /** When the provider's answer is from (ISO). */
365
+ asOf?: string | null;
366
+ }
367
+ /**
368
+ * The one shape a SmartLinks event has when it leaves the platform — what event-sourced flows hand a
369
+ * connector's `serialize` / `send`, after the flow's identity policy and event mapping.
370
+ */
371
+ export interface EventEnvelope {
372
+ /** The event's id: stable across retries; use it as the provider's transaction / idempotency id. */
373
+ id: string;
374
+ /** Spine event name, e.g. 'interaction.submitted:<typeId>', 'contact.updated', 'tag.scanned:scan_redirect'. */
375
+ type: string;
376
+ entityType: string | null;
377
+ entityId: string | null;
378
+ occurredAt: string;
379
+ collectionId: string;
380
+ orgId: string;
381
+ appId: string | null;
382
+ identity: {
383
+ contactId: string | null;
384
+ userId: string | null;
385
+ /** Ids other systems know this person by, keyed by system (a connector's `provider`). */
386
+ externalIds: Record<string, string>;
387
+ emailHash?: string | null;
388
+ phoneHash?: string | null;
389
+ hashAlgorithm?: 'hmac-sha256' | null;
390
+ /** Only under identity policy 'raw'. */
391
+ email?: string | null;
392
+ phone?: string | null;
393
+ };
394
+ identityPolicy: IdentityPolicy;
395
+ product: {
396
+ id: string;
397
+ name?: string | null;
398
+ sku?: string | null;
399
+ gtin?: string | null;
400
+ variantId?: string | null;
401
+ batchId?: string | null;
402
+ } | null;
403
+ proof: {
404
+ id: string;
405
+ } | null;
406
+ /** The event's own fields: an interaction's interactionId / outcome / metadata, a scan's codeId / country, a contact's profile. */
407
+ subject: Record<string, unknown>;
408
+ /** From the flow's event mapping, when it has one. */
409
+ event?: {
410
+ name: string;
411
+ attributes: Record<string, unknown>;
412
+ rule: string | null;
413
+ };
414
+ attributes: Record<string, unknown>;
415
+ }
271
416
  export type RunTrigger = 'manual' | 'event' | 'schedule' | 'test';
272
417
  export type FlowRunStatus = 'running' | 'success' | 'partial' | 'failed' | 'skipped';
273
418
  export type FlowRunItemStatus = 'sent' | 'failed' | 'skipped';