@proveanything/smartlinks 1.15.16 → 1.15.18

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.
@@ -0,0 +1,232 @@
1
+ # Proof Share Grants
2
+
3
+ Delegated, scoped, **revocable** bearer access to a single proof — the middle tier
4
+ between "public" (everyone) and "owner" (only the signed-in owner).
5
+
6
+ A grant lets an owner hand out a link that lets specific recipients **see or do
7
+ specific things on one proof for a limited time**, without those recipients needing
8
+ a SmartLinks account or a proof claim. Typical uses: sharing a private photo album,
9
+ letting guests comment on it, or publishing a verifiable "I own this" assertion.
10
+
11
+ ---
12
+
13
+ ## Concepts
14
+
15
+ A **grant** is a row issued by the proof owner (or a collection admin) and redeemed
16
+ by a bearer holding an opaque token. Every data request that touches the proof
17
+ re-checks the grant **server-side** against the database, so revocation is immediate.
18
+
19
+ ### Scopes — what a grant authorises
20
+
21
+ | Scope | Grants the bearer… |
22
+ |-------|--------------------|
23
+ | `read` | read owner-tier data on the proof (attestations, threads, records, cases) |
24
+ | `comment` | create threads/replies on the proof (guest comments) |
25
+ | `admin` | read owner-tier data (reserved for elevated share cases; never exposes the platform admin zone) |
26
+ | `verify_owner` | redeem a shareable ownership **assertion** (not the account) |
27
+
28
+ A grant can carry several scopes, e.g. `['read', 'comment']` for a shareable,
29
+ commentable album.
30
+
31
+ ### Security & lifecycle
32
+
33
+ - The token is opaque, unguessable, and returned to the issuer **exactly once** (on `createGrant`). It is never returned by `listGrants`.
34
+ - **Revocation is immediate** — the grant is re-checked on every request, so `revokeGrant` invalidates a token across all clients at once.
35
+ - **Auto-invalidation on transfer** — every grant is voided the moment the proof's `ownerId` changes (e.g. a resale/re-claim), so a stale "I own this" link cannot keep resolving.
36
+ - A grant is scoped to **one proof**; it can never widen access to other proofs or collection-level data.
37
+
38
+ ---
39
+
40
+ ## Owner flow — create, list, revoke
41
+
42
+ ```typescript
43
+ import { proof } from '@proveanything/smartlinks'
44
+
45
+ // Create a read+comment grant that expires in 7 days.
46
+ const grant = await proof.createGrant(collectionId, productId, proofId, {
47
+ scope: ['read', 'comment'],
48
+ audience: { kind: 'public_link' },
49
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
50
+ })
51
+
52
+ // `grant.token` is available ONLY here — embed it in your share link now.
53
+ const shareUrl = `https://app.example.com/album?proofId=${proofId}&shareToken=${grant.token}`
54
+
55
+ // List active + past grants (tokens are never included).
56
+ const grants = await proof.listGrants(collectionId, productId, proofId)
57
+
58
+ // Stop sharing — takes effect on the very next request from any client.
59
+ await proof.revokeGrant(collectionId, productId, proofId, grant.grantId)
60
+ ```
61
+
62
+ `createGrant` / `listGrants` / `revokeGrant` require the caller to be the **proof
63
+ owner** (or a collection admin) — i.e. a signed-in user whose `bearerToken` is set.
64
+
65
+ ---
66
+
67
+ ## Recipient flow — redeem, then carry the token
68
+
69
+ A recipient opens the share link, redeems the token once, then sets it as the
70
+ active grant token. From then on **every** SDK request carries the token
71
+ (`X-Grant-Token`), so all proof reads/writes are evaluated against the grant.
72
+
73
+ ```typescript
74
+ import { proof, setGrantToken } from '@proveanything/smartlinks'
75
+
76
+ const shareToken = new URLSearchParams(location.search).get('shareToken')!
77
+
78
+ // Redeem once (anonymous or signed-in). Records the redemption; optionally names the guest.
79
+ await proof.redeemGrant(collectionId, productId, proofId, shareToken, {
80
+ guestName: 'Sam', // stamped on guest activity when not signed in
81
+ })
82
+
83
+ // Attach the token to every subsequent request.
84
+ setGrantToken(shareToken)
85
+
86
+ // Now grant-tier reads succeed — e.g. owner-visibility memories on the proof:
87
+ const { attestations } = await attestation.publicList(collectionId, {
88
+ subjectType: 'proof', subjectId: proofId,
89
+ })
90
+
91
+ // Clear it when leaving the shared view:
92
+ setGrantToken(undefined)
93
+ ```
94
+
95
+ > **Persisting across reloads.** `setGrantToken` holds the token in memory. To keep a
96
+ > shared session across reloads, persist `shareToken` yourself (e.g. in `localStorage`)
97
+ > and call `setGrantToken` again on load.
98
+
99
+ ---
100
+
101
+ ## Guest commenting
102
+
103
+ With a `comment` scope grant, a bearer can post comments even when they are not
104
+ signed in. The app must enable the **grant branch** of the thread create policy
105
+ (see [App config](#app-config)); comments are then created at `visibility: 'owner'`
106
+ (private to the proof) and stamped `authorType: 'guest'`.
107
+
108
+ ```typescript
109
+ import { app } from '@proveanything/smartlinks'
110
+ // setGrantToken(shareToken) has already been called.
111
+
112
+ // One atomic call — no separate create-then-reply round trip.
113
+ await app.threads.create(collectionId, 'photo-memory', {
114
+ parentType: 'memory',
115
+ parentId: memoryId, // text — SmartLinks short ids are fine (not just UUIDs)
116
+ proofId, // anchor to the proof so grant readers see it
117
+ firstReply: { text: 'Lovely photo', authorName: 'Sam' },
118
+ })
119
+ ```
120
+
121
+ Other grant holders (and the owner) see these comments because a `read` grant reveals
122
+ `owner`-visibility threads for the proof. See
123
+ [App Objects → Threads](app-objects.md#threads) for the full threads API.
124
+
125
+ ---
126
+
127
+ ## Proof of ownership — `verify_owner`
128
+
129
+ Ownership itself is **not** a grant — it is `proof.ownerId`, established via the
130
+ existing [claim flow](proof-claiming-methods.md). A `verify_owner` grant only
131
+ publishes a shareable, verifiable **assertion** derived from that ownership, without
132
+ handing over the account:
133
+
134
+ ```typescript
135
+ const grant = await proof.createGrant(collectionId, productId, proofId, {
136
+ scope: ['verify_owner'],
137
+ })
138
+
139
+ // The recipient redeems it and gets the assertion — never the account:
140
+ const result = await proof.redeemGrant(collectionId, productId, proofId, grant.token)
141
+ // { proofId, assertsOwnership: true, ownerDisplayName?, issuedAt, expiresAt }
142
+ ```
143
+
144
+ Because grants auto-invalidate on transfer, a resale cannot leave a stale
145
+ "I own this" link in circulation.
146
+
147
+ ---
148
+
149
+ ## What a grant gates
150
+
151
+ When a valid grant token is present, these public reads elevate to owner-tier for the
152
+ granted proof (and only that proof):
153
+
154
+ - **Attestations** — `attestation.publicList({ subjectType: 'proof', subjectId })`
155
+ - **Threads / Records / Cases** — `app.threads.list`, `app.records.*`, `app.cases.list`, and the single-item GETs, filtered to the granted proof
156
+ - **Thread creation / replies** — with a `comment` scope grant (see below)
157
+
158
+ The token never exposes the platform `admin` zone, and only reveals `owner`-visibility
159
+ rows for the granted `proofId`.
160
+
161
+ ---
162
+
163
+ ## App config
164
+
165
+ Grant-based commenting is opt-in per app, configured on the app's Firestore config
166
+ at `sites/{collectionId}/apps/{appId}` — a `grant` branch alongside
167
+ `anonymous` / `authenticated`:
168
+
169
+ ```jsonc
170
+ {
171
+ "publicCreate": {
172
+ "threads": {
173
+ "grant": {
174
+ "allow": true,
175
+ "requireScope": "comment",
176
+ "enforce": { "visibility": "owner", "status": "open" }
177
+ }
178
+ }
179
+ }
180
+ }
181
+ ```
182
+
183
+ This enables grant-scoped commenting **without** opening up anonymous creation. The
184
+ `enforce.visibility: "owner"` keeps comments private to the proof (visible to the
185
+ owner and other grant holders, not the wider public).
186
+
187
+ ---
188
+
189
+ ## API reference
190
+
191
+ ```typescript
192
+ namespace proof {
193
+ createGrant(collectionId, productId, proofId, options: CreateGrantOptions): Promise<ProofGrant>
194
+ listGrants(collectionId, productId, proofId): Promise<ProofGrant[]>
195
+ revokeGrant(collectionId, productId, proofId, grantId): Promise<void>
196
+ redeemGrant(collectionId, productId, proofId, token, options?: RedeemGrantOptions): Promise<RedeemGrantResult>
197
+ }
198
+
199
+ // Attach / clear the active grant token (sent as X-Grant-Token on every request).
200
+ function setGrantToken(token: string | undefined): void
201
+ function getGrantToken(): string | undefined
202
+
203
+ type GrantScope = 'read' | 'comment' | 'admin' | 'verify_owner'
204
+
205
+ interface CreateGrantOptions {
206
+ scope: GrantScope[] // at least one
207
+ audience?: { kind: 'public_link' } | { kind: 'named'; email?: string; userId?: string }
208
+ expiresAt?: Date | string
209
+ }
210
+
211
+ interface RedeemGrantOptions { guestName?: string }
212
+
213
+ type RedeemGrantResult =
214
+ | { scope: GrantScope[]; redeemedAt: string }
215
+ | { proofId: string; assertsOwnership: true; ownerDisplayName?: string; issuedAt?: string; expiresAt?: string }
216
+
217
+ interface ProofGrant {
218
+ grantId: string
219
+ proofId: string
220
+ productId?: string | null
221
+ scope: GrantScope[]
222
+ audience: { kind: 'public_link' | 'named'; email?: string; userId?: string }
223
+ createdBy: string
224
+ expiresAt?: string | null
225
+ revokedAt?: string | null
226
+ redeemedBy?: { userId?: string; guestName?: string; redeemedAt: string }
227
+ redeemCount: number
228
+ createdAt: string
229
+ updatedAt: string
230
+ token?: string // present ONLY on the createGrant response
231
+ }
232
+ ```
package/dist/http.d.ts CHANGED
@@ -49,6 +49,25 @@ export declare function setExtraHeaders(headers: Record<string, string>): void;
49
49
  * login or logout event.
50
50
  */
51
51
  export declare function setBearerToken(token: string | undefined): void;
52
+ /**
53
+ * Set (or clear) the per-proof share-grant token. When set, it is attached as the
54
+ * `X-Grant-Token` header on every request, so a recipient who has opened a shared
55
+ * link can read/comment on the granted proof's data. Pass `undefined` to clear it.
56
+ *
57
+ * The server re-checks the grant against the database on every request, so calling
58
+ * `revokeGrant` invalidates an in-flight token immediately. Clears the GET cache on
59
+ * change so grant-tier responses are not served after the token changes.
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * // On opening ?proofId=…&shareToken=abc
64
+ * setGrantToken(shareToken)
65
+ * const { attestations } = await proof.get(...) // now sees owner-tier memories
66
+ * ```
67
+ */
68
+ export declare function setGrantToken(token: string | undefined): void;
69
+ /** Returns the currently-set share-grant token, or `undefined`. */
70
+ export declare function getGrantToken(): string | undefined;
52
71
  /**
53
72
  * Returns the bearer token currently held by the SDK, or `undefined` if none is set.
54
73
  * In proxy mode, credentials are held by the parent frame, not the local SDK,
package/dist/http.js CHANGED
@@ -39,6 +39,12 @@ let extraHeadersGlobal = {};
39
39
  * issue refresh tokens and short-lived access tokens. Undefined → web behaviour.
40
40
  */
41
41
  let clientPlatform = undefined;
42
+ /**
43
+ * Per-proof share-grant bearer token. When set (via setGrantToken), it is attached
44
+ * as `X-Grant-Token` on every request so the server can evaluate the grant on any
45
+ * data call that touches the granted proof (attestations, threads, app data).
46
+ */
47
+ let grantToken = undefined;
42
48
  /** Whether initializeApi has been successfully called at least once. */
43
49
  let initialized = false;
44
50
  /** Safely returns the current browser hostname, or an empty string in non-browser / Node environments. */
@@ -466,6 +472,34 @@ export function setBearerToken(token) {
466
472
  if (cachePersistence !== 'none')
467
473
  idbClear().catch(() => { });
468
474
  }
475
+ /**
476
+ * Set (or clear) the per-proof share-grant token. When set, it is attached as the
477
+ * `X-Grant-Token` header on every request, so a recipient who has opened a shared
478
+ * link can read/comment on the granted proof's data. Pass `undefined` to clear it.
479
+ *
480
+ * The server re-checks the grant against the database on every request, so calling
481
+ * `revokeGrant` invalidates an in-flight token immediately. Clears the GET cache on
482
+ * change so grant-tier responses are not served after the token changes.
483
+ *
484
+ * @example
485
+ * ```ts
486
+ * // On opening ?proofId=…&shareToken=abc
487
+ * setGrantToken(shareToken)
488
+ * const { attestations } = await proof.get(...) // now sees owner-tier memories
489
+ * ```
490
+ */
491
+ export function setGrantToken(token) {
492
+ if (token === grantToken)
493
+ return;
494
+ grantToken = token;
495
+ httpCache.clear();
496
+ if (cachePersistence !== 'none')
497
+ idbClear().catch(() => { });
498
+ }
499
+ /** Returns the currently-set share-grant token, or `undefined`. */
500
+ export function getGrantToken() {
501
+ return grantToken;
502
+ }
469
503
  /**
470
504
  * Returns the bearer token currently held by the SDK, or `undefined` if none is set.
471
505
  * In proxy mode, credentials are held by the parent frame, not the local SDK,
@@ -1107,6 +1141,10 @@ export async function request(path) {
1107
1141
  headers["AUTHORIZATION"] = `Bearer ${bearerToken}`;
1108
1142
  if (clientPlatform)
1109
1143
  headers["X-Client-Platform"] = clientPlatform;
1144
+ if (grantToken)
1145
+ headers["X-Grant-Token"] = grantToken;
1146
+ if (grantToken)
1147
+ headers["X-Grant-Token"] = grantToken;
1110
1148
  if (ngrokSkipBrowserWarning)
1111
1149
  headers["ngrok-skip-browser-warning"] = "true";
1112
1150
  const _getDomain = getSourceDomain();
@@ -1182,6 +1220,8 @@ export async function post(path, body, extraHeaders) {
1182
1220
  headers["AUTHORIZATION"] = `Bearer ${bearerToken}`;
1183
1221
  if (clientPlatform)
1184
1222
  headers["X-Client-Platform"] = clientPlatform;
1223
+ if (grantToken)
1224
+ headers["X-Grant-Token"] = grantToken;
1185
1225
  if (ngrokSkipBrowserWarning)
1186
1226
  headers["ngrok-skip-browser-warning"] = "true";
1187
1227
  const _postDomain = getSourceDomain();
@@ -1241,6 +1281,8 @@ export async function put(path, body, extraHeaders) {
1241
1281
  headers["AUTHORIZATION"] = `Bearer ${bearerToken}`;
1242
1282
  if (clientPlatform)
1243
1283
  headers["X-Client-Platform"] = clientPlatform;
1284
+ if (grantToken)
1285
+ headers["X-Grant-Token"] = grantToken;
1244
1286
  if (ngrokSkipBrowserWarning)
1245
1287
  headers["ngrok-skip-browser-warning"] = "true";
1246
1288
  const _putDomain = getSourceDomain();
@@ -1300,6 +1342,8 @@ export async function patch(path, body, extraHeaders) {
1300
1342
  headers["AUTHORIZATION"] = `Bearer ${bearerToken}`;
1301
1343
  if (clientPlatform)
1302
1344
  headers["X-Client-Platform"] = clientPlatform;
1345
+ if (grantToken)
1346
+ headers["X-Grant-Token"] = grantToken;
1303
1347
  if (ngrokSkipBrowserWarning)
1304
1348
  headers["ngrok-skip-browser-warning"] = "true";
1305
1349
  const _patchDomain = getSourceDomain();
@@ -1404,7 +1448,7 @@ export async function requestWithOptions(path, options) {
1404
1448
  }
1405
1449
  }
1406
1450
  const _rwoDomain = getSourceDomain();
1407
- const headers = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ "Content-Type": "application/json" }, (apiKey ? { "X-API-Key": apiKey } : {})), (bearerToken ? { "AUTHORIZATION": `Bearer ${bearerToken}` } : {})), (clientPlatform ? { "X-Client-Platform": clientPlatform } : {})), (ngrokSkipBrowserWarning ? { "ngrok-skip-browser-warning": "true" } : {})), (_rwoDomain ? { "X-Source-Domain": _rwoDomain } : {})), extraHeaders);
1451
+ const headers = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ "Content-Type": "application/json" }, (apiKey ? { "X-API-Key": apiKey } : {})), (bearerToken ? { "AUTHORIZATION": `Bearer ${bearerToken}` } : {})), (clientPlatform ? { "X-Client-Platform": clientPlatform } : {})), (grantToken ? { "X-Grant-Token": grantToken } : {})), (ngrokSkipBrowserWarning ? { "ngrok-skip-browser-warning": "true" } : {})), (_rwoDomain ? { "X-Source-Domain": _rwoDomain } : {})), extraHeaders);
1408
1452
  // Merge global custom headers (do not override existing keys from options.headers)
1409
1453
  for (const [k, v] of Object.entries(extraHeadersGlobal))
1410
1454
  if (!(k in headers))
@@ -1510,6 +1554,8 @@ export async function del(path, extraHeaders) {
1510
1554
  headers["AUTHORIZATION"] = `Bearer ${bearerToken}`;
1511
1555
  if (clientPlatform)
1512
1556
  headers["X-Client-Platform"] = clientPlatform;
1557
+ if (grantToken)
1558
+ headers["X-Grant-Token"] = grantToken;
1513
1559
  if (ngrokSkipBrowserWarning)
1514
1560
  headers["ngrok-skip-browser-warning"] = "true";
1515
1561
  const _delDomain = getSourceDomain();
@@ -1552,6 +1598,8 @@ export function getApiHeaders() {
1552
1598
  headers["AUTHORIZATION"] = `Bearer ${bearerToken}`;
1553
1599
  if (clientPlatform)
1554
1600
  headers["X-Client-Platform"] = clientPlatform;
1601
+ if (grantToken)
1602
+ headers["X-Grant-Token"] = grantToken;
1555
1603
  if (ngrokSkipBrowserWarning)
1556
1604
  headers["ngrok-skip-browser-warning"] = "true";
1557
1605
  const sourceDomain = getSourceDomain();
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken } from "./http";
1
+ export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken, setGrantToken, getGrantToken } from "./http";
2
2
  export * from "./api";
3
3
  export * from "./types";
4
4
  export { iframe } from "./iframe";
@@ -17,7 +17,7 @@ export type { AdditionalGtin, ISODateString, JsonPrimitive, JsonValue, ProductCr
17
17
  export type { TranslationLookupMode, TranslationContentType, TranslationQuality, TranslationItemStatus, TranslationContextValue, TranslationContext, TranslationLookupRequestBase, TranslationLookupSingleRequest, TranslationLookupBatchRequest, TranslationLookupRequest, TranslationLookupItem, TranslationLookupResponse, ResolvedTranslationItem, ResolvedTranslationResponse, TranslationHashOptions, TranslationResolveOptions, TranslationRecord, TranslationListParams, TranslationListResponse, TranslationUpdateRequest, } from "./types/translations";
18
18
  export type { FacetBucket, FacetDefinition, FacetDefinitionWriteInput, FacetGetParams, FacetListParams, FacetListResponse, FacetNamespaceListResponse, FacetQueryRequest, FacetQueryResponse, FacetValue, FacetValueDefinition, FacetValueGetParams, FacetValueListParams, FacetValueListResponse, FacetValueResponse, FacetValueWriteInput, PublicFacetListParams, } from "./types/facets";
19
19
  export type { Collection, CollectionResponse, CollectionCreateRequest, CollectionUpdateRequest, DomainTarget, HubAvailabilityResponse, } from "./types/collection";
20
- export type { Proof, ProofResponse, ProofCreateRequest, ProofUpdateRequest, ProofClaimRequest, } from "./types/proof";
20
+ export type { Proof, ProofResponse, ProofCreateRequest, ProofUpdateRequest, ProofClaimRequest, ProofGrant, GrantScope, GrantAudience, CreateGrantOptions, RedeemGrantOptions, RedeemGrantResult, } from "./types/proof";
21
21
  export type { QrShortCodeLookupResponse, } from "./types/qr";
22
22
  export type { ReverseTagLookupParams, ReverseTagLookupResponse, } from "./types/tags";
23
23
  export type { AdminMobileCapability, ActionableCapability, AdminMobileHostId, AdminMobileEvent, AdminMobileEventCallback, AdminMobileEventSubscriber, ScannerEventSubscriber, // @deprecated — use AdminMobileEventCallback
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  // Top-level entrypoint of the npm package. Re-export initializeApi + all namespaces.
3
- export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken } from "./http";
3
+ export { initializeApi, isInitialized, hasAuthCredentials, configureSdkCache, invalidateCache, request, post, put, patch, del, sendCustomProxyMessage, getApiHeaders, isProxyEnabled, setBearerToken, getBearerToken, setGrantToken, getGrantToken } from "./http";
4
4
  export * from "./api";
5
5
  export * from "./types";
6
6
  // Iframe namespace
package/dist/openapi.yaml CHANGED
@@ -12183,6 +12183,90 @@ paths:
12183
12183
  application/json:
12184
12184
  schema:
12185
12185
  $ref: "#/components/schemas/ProofClaimRequest"
12186
+ /public/collection/{collectionId}/product/{productId}/proof/{proofId}/grant/redeem:
12187
+ post:
12188
+ tags:
12189
+ - proof
12190
+ summary: Redeem a grant token (anonymous or signed-in).
12191
+ operationId: proof_redeemGrant
12192
+ security: []
12193
+ parameters:
12194
+ - name: collectionId
12195
+ in: path
12196
+ required: true
12197
+ schema:
12198
+ type: string
12199
+ - name: productId
12200
+ in: path
12201
+ required: true
12202
+ schema:
12203
+ type: string
12204
+ - name: proofId
12205
+ in: path
12206
+ required: true
12207
+ schema:
12208
+ type: string
12209
+ responses:
12210
+ 200:
12211
+ description: Success
12212
+ content:
12213
+ application/json:
12214
+ schema:
12215
+ $ref: "#/components/schemas/RedeemGrantResult"
12216
+ 400:
12217
+ description: Bad request
12218
+ 401:
12219
+ description: Unauthorized
12220
+ 404:
12221
+ description: Not found
12222
+ requestBody:
12223
+ required: true
12224
+ content:
12225
+ application/json:
12226
+ schema:
12227
+ $ref: "#/components/schemas/RedeemGrantOptions"
12228
+ /public/collection/{collectionId}/product/{productId}/proof/{proofId}/grant/{grantId}:
12229
+ delete:
12230
+ tags:
12231
+ - proof
12232
+ summary: Revoke a grant by id (owner / collection admin only).
12233
+ operationId: proof_revokeGrant
12234
+ security: []
12235
+ parameters:
12236
+ - name: collectionId
12237
+ in: path
12238
+ required: true
12239
+ schema:
12240
+ type: string
12241
+ - name: productId
12242
+ in: path
12243
+ required: true
12244
+ schema:
12245
+ type: string
12246
+ - name: proofId
12247
+ in: path
12248
+ required: true
12249
+ schema:
12250
+ type: string
12251
+ - name: grantId
12252
+ in: path
12253
+ required: true
12254
+ schema:
12255
+ type: string
12256
+ responses:
12257
+ 200:
12258
+ description: Success
12259
+ content:
12260
+ application/json:
12261
+ schema:
12262
+ type: object
12263
+ additionalProperties: true
12264
+ 400:
12265
+ description: Bad request
12266
+ 401:
12267
+ description: Unauthorized
12268
+ 404:
12269
+ description: Not found
12186
12270
  /public/collection/{collectionId}/products/{productId}/createClaim:
12187
12271
  post:
12188
12272
  tags:
@@ -13857,6 +13941,23 @@ paths:
13857
13941
  required: false
13858
13942
  schema:
13859
13943
  type: string
13944
+ - name: parentIds
13945
+ in: query
13946
+ required: false
13947
+ schema:
13948
+ type: array
13949
+ items:
13950
+ type: string
13951
+ - name: proofId
13952
+ in: query
13953
+ required: false
13954
+ schema:
13955
+ type: string
13956
+ - name: productId
13957
+ in: query
13958
+ required: false
13959
+ schema:
13960
+ type: string
13860
13961
  - name: tag
13861
13962
  in: query
13862
13963
  required: false
@@ -14098,6 +14199,52 @@ paths:
14098
14199
  application/json:
14099
14200
  schema:
14100
14201
  $ref: "#/components/schemas/ReplyInput"
14202
+ /{zone}/collection/{collectionId}/app/{appId}/threads/{threadId}/reply/{replyId}:
14203
+ delete:
14204
+ tags:
14205
+ - threads
14206
+ summary: Delete a single reply from a thread by its reply id (moderation).
14207
+ operationId: threads_deleteReply
14208
+ security: []
14209
+ parameters:
14210
+ - name: zone
14211
+ in: path
14212
+ required: true
14213
+ schema:
14214
+ type: string
14215
+ - name: collectionId
14216
+ in: path
14217
+ required: true
14218
+ schema:
14219
+ type: string
14220
+ - name: appId
14221
+ in: path
14222
+ required: true
14223
+ schema:
14224
+ type: string
14225
+ - name: threadId
14226
+ in: path
14227
+ required: true
14228
+ schema:
14229
+ type: string
14230
+ - name: replyId
14231
+ in: path
14232
+ required: true
14233
+ schema:
14234
+ type: string
14235
+ responses:
14236
+ 200:
14237
+ description: Success
14238
+ content:
14239
+ application/json:
14240
+ schema:
14241
+ $ref: "#/components/schemas/AppThread"
14242
+ 400:
14243
+ description: Bad request
14244
+ 401:
14245
+ description: Unauthorized
14246
+ 404:
14247
+ description: Not found
14101
14248
  components:
14102
14249
  securitySchemes:
14103
14250
  bearerAuth:
@@ -16883,6 +17030,8 @@ components:
16883
17030
  admin:
16884
17031
  type: object
16885
17032
  additionalProperties: true
17033
+ firstReply:
17034
+ $ref: "#/components/schemas/ReplyInput"
16886
17035
  UpdateThreadInput:
16887
17036
  type: object
16888
17037
  properties:
@@ -16928,6 +17077,14 @@ components:
16928
17077
  type: string
16929
17078
  parentId:
16930
17079
  type: string
17080
+ parentIds:
17081
+ type: array
17082
+ items:
17083
+ type: string
17084
+ proofId:
17085
+ type: string
17086
+ productId:
17087
+ type: string
16931
17088
  tag:
16932
17089
  type: string
16933
17090
  contactId:
@@ -24865,6 +25022,80 @@ components:
24865
25022
  $ref: "#/components/schemas/ProofFieldDef"
24866
25023
  required:
24867
25024
  - fields
25025
+ GrantAudience:
25026
+ type: object
25027
+ properties:
25028
+ kind:
25029
+ type: string
25030
+ enum:
25031
+ - public_link
25032
+ - named
25033
+ email:
25034
+ type: string
25035
+ userId:
25036
+ type: string
25037
+ required:
25038
+ - kind
25039
+ ProofGrant:
25040
+ type: object
25041
+ properties:
25042
+ grantId:
25043
+ type: string
25044
+ proofId:
25045
+ type: string
25046
+ productId:
25047
+ type: string
25048
+ scope:
25049
+ type: array
25050
+ items:
25051
+ $ref: "#/components/schemas/GrantScope"
25052
+ audience:
25053
+ $ref: "#/components/schemas/GrantAudience"
25054
+ createdBy:
25055
+ type: string
25056
+ expiresAt:
25057
+ type: string
25058
+ revokedAt:
25059
+ type: string
25060
+ redeemedBy:
25061
+ type: object
25062
+ additionalProperties: true
25063
+ redeemCount:
25064
+ type: number
25065
+ createdAt:
25066
+ type: string
25067
+ updatedAt:
25068
+ type: string
25069
+ token:
25070
+ type: string
25071
+ required:
25072
+ - grantId
25073
+ - proofId
25074
+ - scope
25075
+ - audience
25076
+ - createdBy
25077
+ - redeemCount
25078
+ - createdAt
25079
+ - updatedAt
25080
+ CreateGrantOptions:
25081
+ type: object
25082
+ properties:
25083
+ scope:
25084
+ type: array
25085
+ items:
25086
+ $ref: "#/components/schemas/GrantScope"
25087
+ audience:
25088
+ $ref: "#/components/schemas/GrantAudience"
25089
+ expiresAt:
25090
+ type: object
25091
+ additionalProperties: true
25092
+ required:
25093
+ - scope
25094
+ RedeemGrantOptions:
25095
+ type: object
25096
+ properties:
25097
+ guestName:
25098
+ type: string
24868
25099
  ProofResponse:
24869
25100
  type: object
24870
25101
  additionalProperties: true