@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.
- package/dist/api/attestations.d.ts +39 -10
- package/dist/api/attestations.js +42 -9
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.js +2 -0
- package/dist/api/integrations.d.ts +28 -0
- package/dist/api/integrations.js +82 -0
- package/dist/api/proof.js +3 -0
- package/dist/api/secrets.d.ts +15 -0
- package/dist/api/secrets.js +52 -0
- package/dist/docs/API_SUMMARY.md +284 -3
- package/dist/docs/attestations.md +19 -0
- package/dist/docs/integrations.md +141 -0
- package/dist/docs/proof-share-grants.md +78 -5
- package/dist/openapi.yaml +352 -1
- package/dist/types/attestations.d.ts +51 -4
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/integrations.d.ts +136 -0
- package/dist/types/integrations.js +10 -0
- package/dist/types/proof.d.ts +22 -2
- package/docs/API_SUMMARY.md +284 -3
- package/docs/attestations.md +19 -0
- package/docs/integrations.md +141 -0
- package/docs/proof-share-grants.md +78 -5
- package/openapi.yaml +352 -1
- package/package.json +2 -2
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
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
|
|
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` |
|
|
@@ -22,11 +22,12 @@ re-checks the grant **server-side** against the database, so revocation is immed
|
|
|
22
22
|
|-------|--------------------|
|
|
23
23
|
| `read` | read owner-tier data on the proof (attestations, threads, records, cases) |
|
|
24
24
|
| `comment` | create threads/replies on the proof (guest comments) |
|
|
25
|
+
| `contribute` | add attestations / records to the proof for the life of the grant (temporary contribute access) — optionally held for owner review via `moderate` |
|
|
25
26
|
| `admin` | read owner-tier data (reserved for elevated share cases; never exposes the platform admin zone) |
|
|
26
27
|
| `verify_owner` | redeem a shareable ownership **assertion** (not the account) |
|
|
27
28
|
|
|
28
29
|
A grant can carry several scopes, e.g. `['read', 'comment']` for a shareable,
|
|
29
|
-
commentable album.
|
|
30
|
+
commentable album, or `['read', 'contribute']` to let someone add photos.
|
|
30
31
|
|
|
31
32
|
### Security & lifecycle
|
|
32
33
|
|
|
@@ -124,6 +125,65 @@ Other grant holders (and the owner) see these comments because a `read` grant re
|
|
|
124
125
|
|
|
125
126
|
---
|
|
126
127
|
|
|
128
|
+
## Contribute access — let someone add to a proof
|
|
129
|
+
|
|
130
|
+
A `contribute` scope grant is **temporary write access**: the bearer can add
|
|
131
|
+
attestations (a photo, a video reference, a check-in) and app records to the proof
|
|
132
|
+
for the life of the grant — no account or proof claim required. It's the successor
|
|
133
|
+
to ad-hoc "claim windows": properly time-boxed (`expiresAt`), revocable, and voided
|
|
134
|
+
on ownership transfer like every other grant.
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Owner issues a contribute grant that expires in 48h and holds contributions
|
|
138
|
+
// for review before they go public.
|
|
139
|
+
const grant = await proof.createGrant(collectionId, productId, proofId, {
|
|
140
|
+
scope: ['read', 'contribute'],
|
|
141
|
+
moderate: true, // contributions land 'pending'
|
|
142
|
+
expiresAt: new Date(Date.now() + 48 * 60 * 60 * 1000),
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Contributor (on the shared link) adds a photo attestation.
|
|
146
|
+
setGrantToken(shareToken)
|
|
147
|
+
await attestation.publicCreate(collectionId, {
|
|
148
|
+
subjectType: 'proof', subjectId: proofId,
|
|
149
|
+
attestationType: 'photo',
|
|
150
|
+
value: { url: 'https://…/photo.jpg' },
|
|
151
|
+
visibility: 'public',
|
|
152
|
+
guestName: 'Sam', // attribution for a public-link bearer
|
|
153
|
+
})
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Moderation — the two-step review
|
|
157
|
+
|
|
158
|
+
When the grant is issued with `moderate: true`, each contribution is created with
|
|
159
|
+
`moderationStatus: 'pending'` and is **held to the owner**: it is returned only to
|
|
160
|
+
its own author and to owner/admin audiences — never to the public, whatever its
|
|
161
|
+
target `visibility`. The owner reviews and approves (or rejects) it:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// Owner lists what's waiting (owner-tier read = identity or an owner session):
|
|
165
|
+
const { attestations } = await attestation.publicList(collectionId, {
|
|
166
|
+
subjectType: 'proof', subjectId: proofId,
|
|
167
|
+
moderationStatus: 'pending',
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
// Approve → the record goes live at its declared visibility; reject → author+admin only.
|
|
171
|
+
await attestation.moderate(collectionId, attestations[0].id, { decision: 'approve' })
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The moderation gate is enforced **server-side** — a front end may badge a pending
|
|
175
|
+
item, but it is the server that withholds it from other viewers. Only
|
|
176
|
+
`moderationStatus` changes on approve/reject; the hashed fact and its chain are
|
|
177
|
+
untouched. Contributions under a grant issued with `moderate: false` (the default)
|
|
178
|
+
go live immediately.
|
|
179
|
+
|
|
180
|
+
> **Media note.** A contributed photo/video is best modelled as an attestation
|
|
181
|
+
> (`attestationType: 'photo'`, `value: { url }`) so it rides this moderation model.
|
|
182
|
+
> The legacy per-proof asset upload path is unchanged and is not grant- or
|
|
183
|
+
> moderation-aware.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
127
187
|
## Proof of ownership — `verify_owner`
|
|
128
188
|
|
|
129
189
|
Ownership itself is **not** a grant — it is `proof.ownerId`, established via the
|
|
@@ -154,6 +214,7 @@ granted proof (and only that proof):
|
|
|
154
214
|
- **Attestations** — `attestation.publicList({ subjectType: 'proof', subjectId })`
|
|
155
215
|
- **Threads / Records / Cases** — `app.threads.list`, `app.records.*`, `app.cases.list`, and the single-item GETs, filtered to the granted proof
|
|
156
216
|
- **Thread creation / replies** — with a `comment` scope grant (see below)
|
|
217
|
+
- **Attestation / record creation** — with a `contribute` scope grant (see [Contribute access](#contribute-access--let-someone-add-to-a-proof))
|
|
157
218
|
|
|
158
219
|
The token never exposes the platform `admin` zone, and only reveals `owner`-visibility
|
|
159
220
|
rows for the granted `proofId`.
|
|
@@ -175,14 +236,24 @@ at `sites/{collectionId}/apps/{appId}` — a `grant` branch alongside
|
|
|
175
236
|
"requireScope": "comment",
|
|
176
237
|
"enforce": { "visibility": "owner", "status": "open" }
|
|
177
238
|
}
|
|
239
|
+
},
|
|
240
|
+
"records": {
|
|
241
|
+
"grant": {
|
|
242
|
+
"allow": true,
|
|
243
|
+
"requireScope": "contribute",
|
|
244
|
+
"enforce": { "visibility": "owner" } // held to the owner until promoted
|
|
245
|
+
}
|
|
178
246
|
}
|
|
179
247
|
}
|
|
180
248
|
}
|
|
181
249
|
```
|
|
182
250
|
|
|
183
|
-
This enables grant-scoped commenting **without** opening up
|
|
184
|
-
`enforce.visibility: "owner"` keeps
|
|
185
|
-
owner and other grant holders, not the wider
|
|
251
|
+
This enables grant-scoped commenting and contribution **without** opening up
|
|
252
|
+
anonymous creation. The `enforce.visibility: "owner"` keeps the created object
|
|
253
|
+
private to the proof (visible to the owner and other grant holders, not the wider
|
|
254
|
+
public). Attestation contribution is gated the same way, by the `contribute` scope;
|
|
255
|
+
whether those contributions are held for review is set per-grant with `moderate`,
|
|
256
|
+
not in app config.
|
|
186
257
|
|
|
187
258
|
---
|
|
188
259
|
|
|
@@ -200,12 +271,13 @@ namespace proof {
|
|
|
200
271
|
function setGrantToken(token: string | undefined): void
|
|
201
272
|
function getGrantToken(): string | undefined
|
|
202
273
|
|
|
203
|
-
type GrantScope = 'read' | 'comment' | 'admin' | 'verify_owner'
|
|
274
|
+
type GrantScope = 'read' | 'comment' | 'contribute' | 'admin' | 'verify_owner'
|
|
204
275
|
|
|
205
276
|
interface CreateGrantOptions {
|
|
206
277
|
scope: GrantScope[] // at least one
|
|
207
278
|
audience?: { kind: 'public_link' } | { kind: 'named'; email?: string; userId?: string }
|
|
208
279
|
expiresAt?: Date | string
|
|
280
|
+
moderate?: boolean // 'contribute' only — hold for review
|
|
209
281
|
}
|
|
210
282
|
|
|
211
283
|
interface RedeemGrantOptions { guestName?: string }
|
|
@@ -219,6 +291,7 @@ interface ProofGrant {
|
|
|
219
291
|
proofId: string
|
|
220
292
|
productId?: string | null
|
|
221
293
|
scope: GrantScope[]
|
|
294
|
+
moderate?: boolean // 'contribute' grants: contributions held for review when true
|
|
222
295
|
audience: { kind: 'public_link' | 'named'; email?: string; userId?: string }
|
|
223
296
|
createdBy: string
|
|
224
297
|
expiresAt?: string | null
|