@proveanything/smartlinks 1.16.6 → 1.17.0

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.
@@ -164,8 +164,16 @@ export type ProofFieldDef = ScopedFieldDef & {
164
164
  export interface ProofFieldsConfig {
165
165
  fields: ProofFieldDef[];
166
166
  }
167
- /** What a grant authorises the bearer to do on the proof. */
168
- export type GrantScope = 'read' | 'comment' | 'admin' | 'verify_owner';
167
+ /**
168
+ * What a grant authorises the bearer to do on the proof.
169
+ * - `read` — see owner-tier data
170
+ * - `comment` — post app threads/comments
171
+ * - `contribute` — add records / attestations (temporary contribute access); pair
172
+ * with `moderate` on {@link CreateGrantOptions} to hold contributions for review
173
+ * - `admin` — owner-tier read across the proof
174
+ * - `verify_owner` — redeem an ownership assertion (never the account)
175
+ */
176
+ export type GrantScope = 'read' | 'comment' | 'contribute' | 'admin' | 'verify_owner';
169
177
  /** Who may redeem a grant. */
170
178
  export interface GrantAudience {
171
179
  kind: 'public_link' | 'named';
@@ -178,6 +186,11 @@ export interface ProofGrant {
178
186
  proofId: string;
179
187
  productId?: string | null;
180
188
  scope: GrantScope[];
189
+ /**
190
+ * `contribute` grants only: when true, records/attestations added under this
191
+ * grant land `pending` (owner-only) until the owner approves them.
192
+ */
193
+ moderate?: boolean;
181
194
  audience: GrantAudience;
182
195
  createdBy: string;
183
196
  expiresAt?: string | null;
@@ -200,6 +213,13 @@ export interface CreateGrantOptions {
200
213
  audience?: GrantAudience;
201
214
  /** Optional expiry — a `Date` or ISO string. */
202
215
  expiresAt?: Date | string;
216
+ /**
217
+ * Only meaningful with the `contribute` scope: hold contributions made under
218
+ * this grant for owner review (they start `pending` and are owner-only until
219
+ * approved). Ignored for other scopes. Defaults to `false` (contributions live
220
+ * on write).
221
+ */
222
+ moderate?: boolean;
203
223
  }
204
224
  export interface RedeemGrantOptions {
205
225
  /** Display name to stamp on guest activity when the redeemer is not signed in. */
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.16.6 | Generated: 2026-09-06T07:49:21.444Z
3
+ Version: 1.17.0 | Generated: 2026-09-13T07:38:31.861Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -134,6 +134,7 @@ The Smartlinks SDK is organized into the following namespaces:
134
134
  - **containers** - Functions for containers operations
135
135
  - **facets** - Functions for facets operations
136
136
  - **http** - Functions for http operations
137
+ - **integrations** - Functions for integrations operations
137
138
  - **jobs** - Functions for jobs operations
138
139
  - **journeysAnalytics** - Functions for journeysAnalytics operations
139
140
  - **location** - Functions for location operations
@@ -142,6 +143,7 @@ The Smartlinks SDK is organized into the following namespaces:
142
143
  - **order** - Functions for order operations
143
144
  - **products** - Functions for products operations
144
145
  - **realtime** - Functions for realtime operations
146
+ - **secrets** - Functions for secrets operations
145
147
  - **tags** - Functions for tags operations
146
148
  - **template** - Functions for template operations
147
149
  - **translations** - Functions for translations operations
@@ -2745,6 +2747,15 @@ interface Attestation {
2745
2747
  unit?: string
2746
2748
  source?: string
2747
2749
  authorId?: string
2750
+ * When authored under a `contribute` grant (rather than by identity), the id of
2751
+ * the granting token — provenance for a contributed record. `null`/absent for
2752
+ * owner/admin/identity writes.
2753
+ grantId?: string | null
2754
+ * Moderation gate, orthogonal to {@link visibility} and excluded from the hash
2755
+ * chain. `'approved'` (default) is live; `'pending'` is held for owner review
2756
+ * (visible only to its author and owner/admin audiences); `'rejected'` was
2757
+ * declined. Contributions under a `moderate` grant start `'pending'`.
2758
+ moderationStatus?: AttestationModerationStatus
2748
2759
  metadata?: Record<string, any>
2749
2760
  contentHash: string
2750
2761
  prevHash?: string
@@ -2810,6 +2821,23 @@ interface OwnerAttestationInput {
2810
2821
  unit?: string
2811
2822
  source?: string
2812
2823
  metadata?: Record<string, any>
2824
+ * Attribution for an anonymous (public-link) contribute-grant write. Ignored
2825
+ * for owner writes and for named-grant writes (attributed to the signed-in uid).
2826
+ guestName?: string
2827
+ }
2828
+ ```
2829
+
2830
+ **ModerateAttestationInput** (interface)
2831
+ ```typescript
2832
+ interface ModerateAttestationInput {
2833
+ decision: 'approve' | 'reject'
2834
+ }
2835
+ ```
2836
+
2837
+ **ModerateAttestationResponse** (interface)
2838
+ ```typescript
2839
+ interface ModerateAttestationResponse {
2840
+ attestation: Attestation
2813
2841
  }
2814
2842
  ```
2815
2843
 
@@ -2905,6 +2933,10 @@ interface ListAttestationsParams {
2905
2933
  subjectType: AttestationSubjectType
2906
2934
  subjectId: string
2907
2935
  attestationType?: string
2936
+ * Filter by moderation state. Primarily for the owner review queue
2937
+ * (`moderationStatus: 'pending'`). ANDs with the server's audience gate, so a
2938
+ * public caller can never use it to widen access.
2939
+ moderationStatus?: AttestationModerationStatus
2908
2940
  recordedAfter?: string
2909
2941
  recordedBefore?: string
2910
2942
  limit?: number
@@ -2969,6 +3001,8 @@ interface AttestationTreeLatestParams {
2969
3001
 
2970
3002
  **AttestationVisibility** = `'public' | 'owner' | 'admin'`
2971
3003
 
3004
+ **AttestationModerationStatus** = `'approved' | 'pending' | 'rejected'`
3005
+
2972
3006
  **AttestationAudience** = `'public' | 'owner' | 'admin'`
2973
3007
 
2974
3008
  **AttestationGroupBy** = `'hour' | 'day' | 'week' | 'month'`
@@ -5736,6 +5770,195 @@ interface UploadDoneMessage {
5736
5770
 
5737
5771
  **UploadMessage** = ``
5738
5772
 
5773
+ ### integrations
5774
+
5775
+ **FieldMapping** (interface)
5776
+ ```typescript
5777
+ interface FieldMapping {
5778
+ targetPath: string
5779
+ sourcePath?: string
5780
+ transformType: TransformType
5781
+ transformExpression?: string
5782
+ }
5783
+ ```
5784
+
5785
+ **FlowConnectionAuth** (interface)
5786
+ ```typescript
5787
+ interface FlowConnectionAuth {
5788
+ method: FlowAuthMethod
5789
+ headerName?: string
5790
+ credentialRef?: string
5791
+ }
5792
+ ```
5793
+
5794
+ **FlowConnection** (interface)
5795
+ ```typescript
5796
+ interface FlowConnection {
5797
+ baseUrl?: string
5798
+ sendEndpoint?: string
5799
+ fetchEndpoint?: string
5800
+ defaultHeaders?: Record<string, string>
5801
+ auth?: FlowConnectionAuth
5802
+ }
5803
+ ```
5804
+
5805
+ **IntegrationFlowConfig** (interface)
5806
+ ```typescript
5807
+ interface IntegrationFlowConfig {
5808
+ connection?: FlowConnection
5809
+ fieldMappings?: FieldMapping[]
5810
+ [key: string]: any
5811
+ }
5812
+ ```
5813
+
5814
+ **IntegrationFlow** (interface)
5815
+ ```typescript
5816
+ interface IntegrationFlow {
5817
+ id: string
5818
+ orgId: string
5819
+ collectionId: string
5820
+ appId: string
5821
+ direction: FlowDirection
5822
+ name: string
5823
+ status: FlowStatus
5824
+ eventTypes: string[]
5825
+ schedule: string | null
5826
+ sourceEntity: string | null
5827
+ targetEntity: string | null
5828
+ config: IntegrationFlowConfig
5829
+ createdBy: string | null
5830
+ createdAt: string
5831
+ updatedAt: string
5832
+ deletedAt?: string | null
5833
+ lastRunAt?: string | null
5834
+ lastRunStatus?: string | null
5835
+ lastRunError?: string | null
5836
+ lastRunCount?: number | null
5837
+ lastPollAt?: string | null
5838
+ lastCursor?: string | null
5839
+ totalSynced?: number | null
5840
+ }
5841
+ ```
5842
+
5843
+ **CreateFlowInput** (interface)
5844
+ ```typescript
5845
+ interface CreateFlowInput {
5846
+ appId: string
5847
+ direction: FlowDirection
5848
+ name: string
5849
+ status?: FlowStatus
5850
+ eventTypes?: string[]
5851
+ schedule?: string | null
5852
+ sourceEntity?: string | null
5853
+ targetEntity?: string | null
5854
+ config?: IntegrationFlowConfig
5855
+ }
5856
+ ```
5857
+
5858
+ **ListFlowsQuery** (interface)
5859
+ ```typescript
5860
+ interface ListFlowsQuery {
5861
+ direction?: FlowDirection
5862
+ status?: FlowStatus
5863
+ appId?: string
5864
+ }
5865
+ ```
5866
+
5867
+ **FlowList** (interface)
5868
+ ```typescript
5869
+ interface FlowList {
5870
+ flows: IntegrationFlow[]
5871
+ }
5872
+ ```
5873
+
5874
+ **RunFlowInput** (interface)
5875
+ ```typescript
5876
+ interface RunFlowInput {
5877
+ entityId?: string
5878
+ }
5879
+ ```
5880
+
5881
+ **RunFlowSummary** (interface)
5882
+ ```typescript
5883
+ interface RunFlowSummary {
5884
+ flowId: string
5885
+ direction: FlowDirection
5886
+ records: number
5887
+ sent: number
5888
+ failed: number
5889
+ status: RunStatus
5890
+ }
5891
+ ```
5892
+
5893
+ **RunFlowEnqueued** (interface)
5894
+ ```typescript
5895
+ interface RunFlowEnqueued {
5896
+ enqueued: true
5897
+ flowId: string
5898
+ entityId: string | null
5899
+ }
5900
+ ```
5901
+
5902
+ **SecretMeta** (interface)
5903
+ ```typescript
5904
+ interface SecretMeta {
5905
+ ref: string
5906
+ name: string | null
5907
+ purpose: string
5908
+ hint: string
5909
+ keyVersion: number
5910
+ createdBy: string | null
5911
+ createdAt: string
5912
+ updatedAt: string
5913
+ rotatedAt?: string | null
5914
+ }
5915
+ ```
5916
+
5917
+ **SecretList** (interface)
5918
+ ```typescript
5919
+ interface SecretList {
5920
+ secrets: SecretMeta[]
5921
+ }
5922
+ ```
5923
+
5924
+ **SetSecretInput** (interface)
5925
+ ```typescript
5926
+ interface SetSecretInput {
5927
+ value: string
5928
+ name?: string
5929
+ purpose?: string
5930
+ }
5931
+ ```
5932
+
5933
+ **SetSecretResult** (interface)
5934
+ ```typescript
5935
+ interface SetSecretResult {
5936
+ ref: string
5937
+ hint: string
5938
+ }
5939
+ ```
5940
+
5941
+ **ListSecretsQuery** (interface)
5942
+ ```typescript
5943
+ interface ListSecretsQuery {
5944
+ purpose?: string
5945
+ }
5946
+ ```
5947
+
5948
+ **FlowDirection** = `'inbound' | 'outbound'`
5949
+
5950
+ **FlowStatus** = `'draft' | 'active' | 'paused' | 'error'`
5951
+
5952
+ **RunStatus** = `'success' | 'partial' | 'error'`
5953
+
5954
+ **TransformType** = `'direct' | 'static' | 'template' | 'jsonata' | 'ai'`
5955
+
5956
+ **FlowAuthMethod** = `'api_key' | 'bearer' | 'basic' | 'webhook' | 'oauth2' | 'none'`
5957
+
5958
+ **UpdateFlowInput** = `Partial<Omit<CreateFlowInput, 'direction'>> & {`
5959
+
5960
+ **RunFlowResult** = `RunFlowSummary | RunFlowEnqueued`
5961
+
5739
5962
  ### interaction
5740
5963
 
5741
5964
  **AdminInteractionsQueryRequest** (interface)
@@ -7564,6 +7787,9 @@ interface ProofGrant {
7564
7787
  proofId: string
7565
7788
  productId?: string | null
7566
7789
  scope: GrantScope[]
7790
+ * `contribute` grants only: when true, records/attestations added under this
7791
+ * grant land `pending` (owner-only) until the owner approves them.
7792
+ moderate?: boolean
7567
7793
  audience: GrantAudience
7568
7794
  createdBy: string
7569
7795
  expiresAt?: string | null
@@ -7582,6 +7808,11 @@ interface CreateGrantOptions {
7582
7808
  scope: GrantScope[]
7583
7809
  audience?: GrantAudience
7584
7810
  expiresAt?: Date | string
7811
+ * Only meaningful with the `contribute` scope: hold contributions made under
7812
+ * this grant for owner review (they start `pending` and are owner-only until
7813
+ * approved). Ignored for other scopes. Defaults to `false` (contributions live
7814
+ * on write).
7815
+ moderate?: boolean
7585
7816
  }
7586
7817
  ```
7587
7818
 
@@ -7679,7 +7910,7 @@ interface CancelTransferOptions {
7679
7910
 
7680
7911
  **ProofFieldDef** = `ScopedFieldDef & { scope?: ProofFieldScope }`
7681
7912
 
7682
- **GrantScope** = `'read' | 'comment' | 'admin' | 'verify_owner'`
7913
+ **GrantScope** = `'read' | 'comment' | 'contribute' | 'admin' | 'verify_owner'`
7683
7914
 
7684
7915
  **RedeemGrantResult** = ``
7685
7916
 
@@ -9088,7 +9319,12 @@ List attestations for a subject (public). Records with `visibility='admin'` are
9088
9319
 
9089
9320
  **publicCreate**(collectionId: string,
9090
9321
  data: OwnerAttestationInput) → `Promise<CreateOwnerAttestationResponse>`
9091
- Create an OWNER-authored attestation (public write) — the counterpart to the admin {@link create}. The authenticated caller must OWN the linked proof (identity, not a read grant). Guardrails enforced server-side: they may write `value` + `ownerData` only (`adminData` is dropped), `visibility` is clamped to `'public' | 'owner'`, and `authorId` is forced to the caller. The record joins the same tamper-evident hash chain. POST /public/collection/:collectionId/attestations ```ts await attestations.publicCreate('coll_123', { subjectType: 'proof', subjectId: 'proof_1', attestationType: 'condition-report', value: { grade: 'excellent' }, visibility: 'public', }) ```
9322
+ Create a public attestation — the counterpart to the admin {@link create}. Authorised two ways, same call (the server decides from the request): 1. the proof OWNER (identity, via `Authorization: Bearer <Firebase ID token>`) adds an attestation to their own item; or 2. a holder of a `contribute`-scope grant adds one — call {@link setGrantToken} with the grant token first; for a public-link (anonymous) grant, pass `guestName` for attribution. Guardrails (server-enforced): `value` + `ownerData` only (`adminData` dropped), `visibility` clamped to `'public' | 'owner'`, `authorId`/`grantId` server-stamped. If the contribute grant was issued with `moderate: true`, the returned record has `moderationStatus: 'pending'` — held to the owner until {@link moderate}. The record joins the same tamper-evident hash chain. POST /public/collection/:collectionId/attestations ```ts // Owner: await attestations.publicCreate('coll_123', { subjectType: 'proof', subjectId: 'proof_1', attestationType: 'condition-report', value: { grade: 'excellent' }, visibility: 'public', }) // Contributor on a shared link: setGrantToken(shareToken) await attestations.publicCreate('coll_123', { subjectType: 'proof', subjectId: 'proof_1', attestationType: 'photo', value: { url }, visibility: 'public', guestName: 'Sam', }) ```
9323
+
9324
+ **moderate**(collectionId: string,
9325
+ attestationId: string,
9326
+ input: ModerateAttestationInput) → `Promise<ModerateAttestationResponse>`
9327
+ Moderate a contributed attestation (proof OWNER by identity, or collection admin). `'approve'` releases it to its declared visibility; `'reject'` keeps it author + admin only. Only the `moderationStatus` changes — the hashed fact and its chain are untouched. Find pending items with {@link publicList} + `moderationStatus: 'pending'`. POST /public/collection/:collectionId/attestations/:attestationId/moderate ```ts await attestations.moderate('coll_123', 'att_uuid', { decision: 'approve' }) ```
9092
9328
 
9093
9329
  **publicSummary**(collectionId: string,
9094
9330
  params: AttestationSummaryParams) → `Promise<PublicAttestationSummaryResponse>`
@@ -9876,6 +10112,34 @@ Perform a PATCH request to any API endpoint.
9876
10112
  **del**(path: string) → `Promise<T>`
9877
10113
  Perform a DELETE request to any API endpoint.
9878
10114
 
10115
+ ### integrations
10116
+
10117
+ **listFlows**(collectionId: string, query: ListFlowsQuery = {}) → `Promise<FlowList>`
10118
+ List flows in a collection. GET /integrations/flows
10119
+
10120
+ **createFlow**(collectionId: string, input: CreateFlowInput) → `Promise<IntegrationFlow>`
10121
+ Create a flow. POST /integrations/flows
10122
+
10123
+ **getFlow**(collectionId: string, id: string) → `Promise<IntegrationFlow>`
10124
+ Get one flow. GET /integrations/flows/:id
10125
+
10126
+ **updateFlow**(collectionId: string, id: string, input: UpdateFlowInput) → `Promise<IntegrationFlow>`
10127
+ Update whitelisted fields. PUT /integrations/flows/:id
10128
+
10129
+ **deleteFlow**(collectionId: string, id: string) → `Promise<`
10130
+ Soft-delete a flow. DELETE /integrations/flows/:id
10131
+
10132
+ **runFlow**(collectionId: string,
10133
+ id: string,
10134
+ options: RunFlowInput & { async?: boolean } = {}) → `Promise<RunFlowResult>`
10135
+ Run a flow now. POST /integrations/flows/:id/run - inline (default): resolves and returns the run summary. - options.async: enqueue on the worker, returns { enqueued: true }. Pass options.entityId to run for a single source entity.
10136
+
10137
+ **isRunSummary**(r: RunFlowResult) → `r is RunFlowSummary`
10138
+ Type guard: the run executed inline and returned a summary.
10139
+
10140
+ **isRunEnqueued**(r: RunFlowResult) → `r is RunFlowEnqueued`
10141
+ Type guard: the run was enqueued (async).
10142
+
9879
10143
  ### interactions
9880
10144
 
9881
10145
  **query**(collectionId: string,
@@ -10568,6 +10832,23 @@ Get an Ably token for public (user-scoped) real-time communication. This endpoin
10568
10832
  **getAdminToken**() → `Promise<AblyTokenRequest>`
10569
10833
  Get an Ably token for admin real-time communication. This endpoint returns an Ably TokenRequest that can be used to initialize an Ably client with admin permissions to receive system notifications and alerts. Admin users get subscribe-only (read-only) access to the interaction:{userId} channel pattern. Requires admin authentication (Bearer token). ```ts const tokenRequest = await realtime.getAdminToken() // Use with Ably const ably = new Ably.Realtime.Promise({ authCallback: async (data, callback) => { callback(null, tokenRequest) } }) // Subscribe to admin interaction channel const userId = 'my-user-id' const channel = ably.channels.get(`interaction:${userId}`) await channel.subscribe((message) => { console.log('Admin notification:', message.data) }) ```
10570
10834
 
10835
+ ### secrets
10836
+
10837
+ **list**(collectionId: string, query: ListSecretsQuery = {}) → `Promise<SecretList>`
10838
+ List secrets as refs + masked hints + metadata (never values). GET /secrets
10839
+
10840
+ **set**(collectionId: string, input: SetSecretInput) → `Promise<SetSecretResult>`
10841
+ Create a secret. POST /secrets → { ref, hint }. Store the ref on a flow.
10842
+
10843
+ **get**(collectionId: string, ref: string) → `Promise<SecretMeta>`
10844
+ Metadata for one secret (never the value). GET /secrets/:ref
10845
+
10846
+ **rotate**(collectionId: string, ref: string, input: SetSecretInput) → `Promise<SetSecretResult>`
10847
+ Rotate/update a secret's value (and optionally name/purpose). PUT /secrets/:ref → { ref, hint }
10848
+
10849
+ **remove**(collectionId: string, ref: string) → `Promise<`
10850
+ Soft-delete a secret. DELETE /secrets/:ref
10851
+
10571
10852
  ### segments
10572
10853
 
10573
10854
  **create**(collectionId: string,
@@ -166,6 +166,25 @@ When ownership is confirmed the request is served at `audience='owner'`, which i
166
166
  - `visibility` is clamped to `'public' | 'owner'` (never `'admin'`), defaulting to `'owner'`;
167
167
  - `authorId` is forced to the caller and `metadata.authorType = 'owner'` is stamped.
168
168
 
169
+ **Contribute write (grant)** — the same endpoint also accepts a holder of a
170
+ `contribute`-scope share grant (set the token with `setGrantToken` first), so a
171
+ third party can add an attestation without owning the proof. Named grant →
172
+ attributed to the signed-in uid; public-link grant → anonymous, with `guestName`
173
+ for attribution; `grantId` is stamped either way. If the grant was issued with
174
+ `moderate: true`, the record is created with `moderationStatus: 'pending'` and is
175
+ held to the owner until approved.
176
+
177
+ **Moderation** — `moderationStatus` (`approved` | `pending` | `rejected`) is a gate
178
+ **orthogonal to `visibility`** and excluded from the hash chain. A `pending` record
179
+ is returned only to its author and to owner/admin audiences — never to the public,
180
+ whatever its target visibility — and is excluded from public analytics. The owner
181
+ (identity) or a collection admin resolves it with `attestations.moderate(collectionId,
182
+ attestationId, { decision: 'approve' | 'reject' })`; approve releases it to its
183
+ declared visibility, reject keeps it author+admin-only. The review queue is
184
+ `attestations.publicList(..., { moderationStatus: 'pending' })`. Flipping the status
185
+ leaves the hashed fact and its chain untouched. See
186
+ [Proof Share Grants → Contribute access](proof-share-grants.md#contribute-access--let-someone-add-to-a-proof).
187
+
169
188
  The record joins the same append-only, hash-chained log as business/system writes. Business writes (any zone/visibility) remain admin-only.
170
189
 
171
190
  ### Visibility vs audience
@@ -0,0 +1,141 @@
1
+ # Integrations
2
+
3
+ An **integration flow** is one input/output pipeline between SmartLinks and an external
4
+ system. There are two directions:
5
+
6
+ - **outbound** — read a SmartLinks entity (v1: a product), transform it with field
7
+ mappings, and send it to an external endpoint.
8
+ - **inbound** — fetch from an external system and write a SmartLinks entity. *(Executor is
9
+ outbound-first; inbound lands in a later increment.)*
10
+
11
+ Flows are triggered three ways, all converging on the same executor:
12
+
13
+ - **manual** — `integrations.runFlow(...)`, inline (returns a run summary) or enqueued.
14
+ - **event** — an outbound flow subscribed to an event type (e.g. `product.updated`) fires
15
+ automatically when that entity changes.
16
+ - **schedule** — a flow carrying a cron/interval `schedule` is run by the scan job. *(next)*
17
+
18
+ Credentials are **never** stored on the flow. The connection holds an opaque
19
+ `credentialRef` into the **sealed-secret store** (`secrets` namespace); the value is sealed
20
+ at rest and resolved server-side only, at execution.
21
+
22
+ ---
23
+
24
+ ## The flow model
25
+
26
+ ```ts
27
+ interface IntegrationFlow {
28
+ id: string
29
+ direction: 'inbound' | 'outbound'
30
+ name: string
31
+ status: 'draft' | 'active' | 'paused' | 'error' // only 'active' flows fire on events/schedule
32
+ eventTypes: string[] // e.g. ['product.updated']
33
+ schedule: string | null // cron/interval for scheduled flows
34
+ sourceEntity: string | null // outbound source, v1: 'product'
35
+ targetEntity: string | null // inbound target
36
+ config: {
37
+ connection?: {
38
+ baseUrl?: string
39
+ sendEndpoint?: string // outbound: appended to baseUrl
40
+ defaultHeaders?: Record<string, string>
41
+ auth?: { method: 'api_key' | 'bearer' | 'basic' | ..., headerName?: string, credentialRef?: string }
42
+ }
43
+ fieldMappings?: FieldMapping[]
44
+ }
45
+ // ...run watermark/telemetry: lastRunAt, lastRunStatus, lastRunCount, totalSynced
46
+ }
47
+ ```
48
+
49
+ ### Field mappings (transform)
50
+
51
+ Each mapping produces one field on the target payload:
52
+
53
+ | transformType | uses | meaning |
54
+ |---|---|---|
55
+ | `direct` | `sourcePath` | copy the value at that dot-path |
56
+ | `static` | `transformExpression` | a constant |
57
+ | `template` | `transformExpression` | a Liquid template rendered against the source record |
58
+ | `jsonata` / `ai` | — | recognised but not yet executed; reported as a per-field error |
59
+
60
+ A single field's failure is collected and the rest continue (partial success) — it never
61
+ aborts the whole record.
62
+
63
+ ---
64
+
65
+ ## Secrets (write-only)
66
+
67
+ The secret store is **write-only from the client**: you can set, rotate, list (refs +
68
+ masked hints + metadata) and delete — but a value never comes back over the API.
69
+
70
+ ```ts
71
+ import { secrets, integrations } from '@proveanything/smartlinks'
72
+
73
+ // 1. Store the destination credential — keep the returned ref.
74
+ const { ref } = await secrets.set(collectionId, {
75
+ name: 'Acme API key',
76
+ purpose: 'integration',
77
+ value: 'sk_live_…', // sent once; never retrievable
78
+ })
79
+
80
+ // list shows refs + masked hints only (safe to render)
81
+ const { secrets: list } = await secrets.list(collectionId)
82
+ // → [{ ref, name: 'Acme API key', hint: '…live_1a2b', purpose, createdAt, ... }]
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Creating and running a flow
88
+
89
+ ```ts
90
+ // 2. Create an outbound flow that pushes products to Acme, authed by the secret above.
91
+ const flow = await integrations.createFlow(collectionId, {
92
+ appId: 'my-integration-app',
93
+ direction: 'outbound',
94
+ name: 'Push products to Acme',
95
+ status: 'active',
96
+ eventTypes: ['product.updated'], // fire whenever a product changes
97
+ sourceEntity: 'product',
98
+ config: {
99
+ connection: {
100
+ baseUrl: 'https://api.acme.example',
101
+ sendEndpoint: '/v1/products',
102
+ auth: { method: 'api_key', headerName: 'X-API-Key', credentialRef: ref },
103
+ },
104
+ fieldMappings: [
105
+ { targetPath: 'sku', sourcePath: 'sku', transformType: 'direct' },
106
+ { targetPath: 'name', sourcePath: 'name', transformType: 'direct' },
107
+ { targetPath: 'label', transformType: 'template', transformExpression: '{{name}} ({{sku}})' },
108
+ ],
109
+ },
110
+ })
111
+
112
+ // 3a. Test it now against one product — inline, returns a summary.
113
+ const result = await integrations.runFlow(collectionId, flow.id, { entityId: 'P1045716' })
114
+ if (integrations.isRunSummary(result)) {
115
+ console.log(result) // { records: 1, sent: 1, failed: 0, status: 'success' }
116
+ }
117
+
118
+ // 3b. Or enqueue on the worker (returns immediately).
119
+ await integrations.runFlow(collectionId, flow.id, { entityId: 'P1045716', async: true })
120
+ ```
121
+
122
+ Once `status: 'active'` with `eventTypes: ['product.updated']`, editing that product in the
123
+ admin API fires the flow automatically — no manual run needed.
124
+
125
+ ---
126
+
127
+ ## Reference
128
+
129
+ | Function | HTTP |
130
+ |---|---|
131
+ | `integrations.listFlows(collectionId, query?)` | `GET /integrations/flows` |
132
+ | `integrations.createFlow(collectionId, input)` | `POST /integrations/flows` |
133
+ | `integrations.getFlow(collectionId, id)` | `GET /integrations/flows/:id` |
134
+ | `integrations.updateFlow(collectionId, id, input)` | `PUT /integrations/flows/:id` |
135
+ | `integrations.deleteFlow(collectionId, id)` | `DELETE /integrations/flows/:id` |
136
+ | `integrations.runFlow(collectionId, id, opts?)` | `POST /integrations/flows/:id/run` |
137
+ | `secrets.list(collectionId, query?)` | `GET /secrets` |
138
+ | `secrets.set(collectionId, input)` | `POST /secrets` |
139
+ | `secrets.get(collectionId, ref)` | `GET /secrets/:ref` |
140
+ | `secrets.rotate(collectionId, ref, input)` | `PUT /secrets/:ref` |
141
+ | `secrets.remove(collectionId, ref)` | `DELETE /secrets/:ref` |