@proveanything/smartlinks 1.16.4 → 1.16.6

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.
@@ -1,4 +1,4 @@
1
- import type { Attestation, CreateAttestationInput, ListAttestationsParams, ListAttestationsResponse, AttestationSummaryParams, AttestationSummaryResponse, PublicAttestationSummaryResponse, AttestationLatestParams, AttestationLatestResponse, PublicAttestationLatestResponse, AttestationVerifyParams, ChainVerifyResult, AttestationTreeSummaryParams, AttestationTreeSummaryResponse, PublicAttestationTreeSummaryResponse, AttestationTreeLatestParams, AttestationTreeLatestResponse, PublicAttestationTreeLatestResponse, PublicListAttestationsResponse } from "../types/attestations";
1
+ import type { Attestation, CreateAttestationInput, ListAttestationsParams, ListAttestationsResponse, AttestationSummaryParams, AttestationSummaryResponse, PublicAttestationSummaryResponse, AttestationLatestParams, AttestationLatestResponse, PublicAttestationLatestResponse, AttestationVerifyParams, ChainVerifyResult, AttestationTreeSummaryParams, AttestationTreeSummaryResponse, PublicAttestationTreeSummaryResponse, AttestationTreeLatestParams, AttestationTreeLatestResponse, PublicAttestationTreeLatestResponse, PublicListAttestationsResponse, OwnerAttestationInput, CreateOwnerAttestationResponse } from "../types/attestations";
2
2
  /**
3
3
  * Postgres-backed Attestations API (v2).
4
4
  *
@@ -10,9 +10,12 @@ import type { Attestation, CreateAttestationInput, ListAttestationsParams, ListA
10
10
  *
11
11
  * ### Admin vs Public
12
12
  * - **Admin** endpoints (`/admin/collection/:id/attestations`) require a valid
13
- * admin session or bearer token. All three data zones are returned.
14
- * - **Public** endpoints (`/public/collection/:id/attestations`) are read-only.
15
- * Owner elevation is available via `Authorization: Bearer <Firebase ID token>`.
13
+ * admin session or bearer token. All three data zones are returned, and the
14
+ * business can write any zone/visibility ({@link create}).
15
+ * - **Public** endpoints (`/public/collection/:id/attestations`) are read-only,
16
+ * EXCEPT {@link publicCreate}: the proof OWNER may author an attestation on
17
+ * their own item (value + ownerData, visibility public|owner — never the admin
18
+ * zone). Owner elevation is via `Authorization: Bearer <Firebase ID token>`.
16
19
  *
17
20
  * @see docs/attestations.md
18
21
  */
@@ -207,6 +210,23 @@ export declare namespace attestations {
207
210
  * ```
208
211
  */
209
212
  function publicList(collectionId: string, params: ListAttestationsParams): Promise<PublicListAttestationsResponse>;
213
+ /**
214
+ * Create an OWNER-authored attestation (public write) — the counterpart to the
215
+ * admin {@link create}. The authenticated caller must OWN the linked proof
216
+ * (identity, not a read grant). Guardrails enforced server-side: they may write
217
+ * `value` + `ownerData` only (`adminData` is dropped), `visibility` is clamped
218
+ * to `'public' | 'owner'`, and `authorId` is forced to the caller. The record
219
+ * joins the same tamper-evident hash chain.
220
+ * POST /public/collection/:collectionId/attestations
221
+ * ```ts
222
+ * await attestations.publicCreate('coll_123', {
223
+ * subjectType: 'proof', subjectId: 'proof_1',
224
+ * attestationType: 'condition-report',
225
+ * value: { grade: 'excellent' }, visibility: 'public',
226
+ * })
227
+ * ```
228
+ */
229
+ function publicCreate(collectionId: string, data: OwnerAttestationInput): Promise<CreateOwnerAttestationResponse>;
210
230
  /**
211
231
  * Time-series summary (public).
212
232
  *
@@ -23,9 +23,12 @@ function buildAttestationQuery(params) {
23
23
  *
24
24
  * ### Admin vs Public
25
25
  * - **Admin** endpoints (`/admin/collection/:id/attestations`) require a valid
26
- * admin session or bearer token. All three data zones are returned.
27
- * - **Public** endpoints (`/public/collection/:id/attestations`) are read-only.
28
- * Owner elevation is available via `Authorization: Bearer <Firebase ID token>`.
26
+ * admin session or bearer token. All three data zones are returned, and the
27
+ * business can write any zone/visibility ({@link create}).
28
+ * - **Public** endpoints (`/public/collection/:id/attestations`) are read-only,
29
+ * EXCEPT {@link publicCreate}: the proof OWNER may author an attestation on
30
+ * their own item (value + ownerData, visibility public|owner — never the admin
31
+ * zone). Owner elevation is via `Authorization: Bearer <Firebase ID token>`.
29
32
  *
30
33
  * @see docs/attestations.md
31
34
  */
@@ -273,6 +276,27 @@ export var attestations;
273
276
  return request(path);
274
277
  }
275
278
  attestations.publicList = publicList;
279
+ /**
280
+ * Create an OWNER-authored attestation (public write) — the counterpart to the
281
+ * admin {@link create}. The authenticated caller must OWN the linked proof
282
+ * (identity, not a read grant). Guardrails enforced server-side: they may write
283
+ * `value` + `ownerData` only (`adminData` is dropped), `visibility` is clamped
284
+ * to `'public' | 'owner'`, and `authorId` is forced to the caller. The record
285
+ * joins the same tamper-evident hash chain.
286
+ * POST /public/collection/:collectionId/attestations
287
+ * ```ts
288
+ * await attestations.publicCreate('coll_123', {
289
+ * subjectType: 'proof', subjectId: 'proof_1',
290
+ * attestationType: 'condition-report',
291
+ * value: { grade: 'excellent' }, visibility: 'public',
292
+ * })
293
+ * ```
294
+ */
295
+ async function publicCreate(collectionId, data) {
296
+ const path = `/public/collection/${encodeURIComponent(collectionId)}/attestations`;
297
+ return post(path, data);
298
+ }
299
+ attestations.publicCreate = publicCreate;
276
300
  /**
277
301
  * Time-series summary (public).
278
302
  *
@@ -187,6 +187,13 @@ export declare namespace proof {
187
187
  * },
188
188
  * })
189
189
  * ```
190
+ * Direct to an SMS recipient by phone (E.164) and notify them by text:
191
+ * ```ts
192
+ * await proof.transfer(collectionId, productId, proofId, {
193
+ * toPhone: '+14155551234',
194
+ * comms: { recipient: { templateId: 'transfer-incoming', channel: 'sms' } },
195
+ * })
196
+ * ```
190
197
  */
191
198
  function transfer(collectionId: string, productId: string, proofId: string, options: TransferProofOptions): Promise<TransferProofResult>;
192
199
  /**
package/dist/api/proof.js CHANGED
@@ -280,6 +280,13 @@ export var proof;
280
280
  * },
281
281
  * })
282
282
  * ```
283
+ * Direct to an SMS recipient by phone (E.164) and notify them by text:
284
+ * ```ts
285
+ * await proof.transfer(collectionId, productId, proofId, {
286
+ * toPhone: '+14155551234',
287
+ * comms: { recipient: { templateId: 'transfer-incoming', channel: 'sms' } },
288
+ * })
289
+ * ```
283
290
  */
284
291
  async function transfer(collectionId, productId, proofId, options) {
285
292
  return post(transferBase(collectionId, productId, proofId), Object.assign({}, options));
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.16.4 | Generated: 2026-09-05T14:13:52.478Z
3
+ Version: 1.16.6 | Generated: 2026-09-06T07:49:21.444Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -2797,6 +2797,22 @@ interface CreateAttestationInput {
2797
2797
  }
2798
2798
  ```
2799
2799
 
2800
+ **OwnerAttestationInput** (interface)
2801
+ ```typescript
2802
+ interface OwnerAttestationInput {
2803
+ subjectType: AttestationSubjectType
2804
+ subjectId: string
2805
+ attestationType: string
2806
+ recordedAt?: string
2807
+ visibility?: 'public' | 'owner'
2808
+ value?: Record<string, any>
2809
+ ownerData?: Record<string, any>
2810
+ unit?: string
2811
+ source?: string
2812
+ metadata?: Record<string, any>
2813
+ }
2814
+ ```
2815
+
2800
2816
  **ListAttestationsResponse** (interface)
2801
2817
  ```typescript
2802
2818
  interface ListAttestationsResponse {
@@ -2804,6 +2820,13 @@ interface ListAttestationsResponse {
2804
2820
  }
2805
2821
  ```
2806
2822
 
2823
+ **CreateOwnerAttestationResponse** (interface)
2824
+ ```typescript
2825
+ interface CreateOwnerAttestationResponse {
2826
+ attestation: Attestation
2827
+ }
2828
+ ```
2829
+
2807
2830
  **PublicListAttestationsResponse** (interface)
2808
2831
  ```typescript
2809
2832
  interface PublicListAttestationsResponse {
@@ -7605,9 +7628,14 @@ interface CommsTrigger {
7605
7628
  ```typescript
7606
7629
  interface TransferProofOptions {
7607
7630
  * Comms to send. Roles: `recipient` (the named new owner) and `sender` (the
7608
- * initiator) for a directed transfer; `owner` for an open release.
7631
+ * initiator) for a directed transfer; `owner` for an open release. For an
7632
+ * SMS recipient, set `comms.recipient.channel = 'sms'`.
7609
7633
  comms?: CommsTriggerMap
7610
7634
  toEmail?: string
7635
+ * Directed: recipient phone in E.164 (e.g. `+14155551234`). Resolves to the same
7636
+ * user they log in as via SMS OTP (created if needed). Pair with an SMS comms
7637
+ * trigger to notify them: `comms.recipient.channel = 'sms'`.
7638
+ toPhone?: string
7611
7639
  toUserId?: string
7612
7640
  toName?: string
7613
7641
  release?: boolean
@@ -9058,6 +9086,10 @@ Tree latest snapshot — most-recent record per type across a container subtree
9058
9086
  params: ListAttestationsParams) → `Promise<PublicListAttestationsResponse>`
9059
9087
  List attestations for a subject (public). Records with `visibility='admin'` are always excluded. Records with `visibility='owner'` are included only when the caller provides a valid Firebase ID token that resolves to the subject owner. The `audience` field in the response indicates the tier that was served. ```typescript const { attestations: records, audience } = await attestations.publicList('coll_123', { subjectType: 'proof', subjectId: 'proof-uuid', }) ```
9060
9088
 
9089
+ **publicCreate**(collectionId: string,
9090
+ 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', }) ```
9092
+
9061
9093
  **publicSummary**(collectionId: string,
9062
9094
  params: AttestationSummaryParams) → `Promise<PublicAttestationSummaryResponse>`
9063
9095
  Time-series summary (public). Always served at `audience='public'`. Same parameters as the admin version. `attestationType` are required
@@ -10470,7 +10502,7 @@ Redeem a grant token (anonymous or signed-in). Records the redemption and return
10470
10502
  productId: string,
10471
10503
  proofId: string,
10472
10504
  options: TransferProofOptions) → `Promise<TransferProofResult>`
10473
- Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ``` Send comms by naming templates per role (server decides who receives each): ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com', comms: { recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } }, sender: { templateId: 'transfer-sent' }, }, }) ```
10505
+ Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ``` Send comms by naming templates per role (server decides who receives each): ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com', comms: { recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } }, sender: { templateId: 'transfer-sent' }, }, }) ``` Direct to an SMS recipient by phone (E.164) and notify them by text: ```ts await proof.transfer(collectionId, productId, proofId, { toPhone: '+14155551234', comms: { recipient: { templateId: 'transfer-incoming', channel: 'sms' } }, }) ```
10474
10506
 
10475
10507
  **acceptTransfer**(collectionId: string,
10476
10508
  productId: string,
@@ -159,6 +159,15 @@ The middleware resolves the UID and checks ownership:
159
159
 
160
160
  When ownership is confirmed the request is served at `audience='owner'`, which includes `ownerData`.
161
161
 
162
+ **Owner write** — the proof owner may also *author* an attestation on their own item via `POST /api/v1/public/collection/:collectionId/attestations` (`attestations.publicCreate`). It is the public counterpart to the admin write, with tight guardrails:
163
+
164
+ - the caller must be authenticated and **own the linked proof** (identity — a read grant is not enough); a subject with no linked proof is rejected;
165
+ - they may write `value` and `ownerData` only — **`adminData` is dropped** (the business zone);
166
+ - `visibility` is clamped to `'public' | 'owner'` (never `'admin'`), defaulting to `'owner'`;
167
+ - `authorId` is forced to the caller and `metadata.authorType = 'owner'` is stamped.
168
+
169
+ The record joins the same append-only, hash-chained log as business/system writes. Business writes (any zone/visibility) remain admin-only.
170
+
162
171
  ### Visibility vs audience
163
172
 
164
173
  `visibility` is a property of an **individual record** set at write time. `audience` describes the **caller's tier** resolved at read time. The server applies:
@@ -128,6 +128,11 @@ Omit `comms` entirely and nothing is sent — comms are **opt-in**.
128
128
 
129
129
  - **Email-only recipients work.** A directed transfer to a `toEmail` that isn't a
130
130
  contact yet resolves/creates a contact before sending.
131
+ - **SMS recipients work the same way.** A directed transfer to a `toPhone` (E.164)
132
+ resolves the recipient by phone and gives their contact a phone identity, so an
133
+ SMS-channel trigger (`channel: 'sms'`) reaches them. Because the phone maps to a
134
+ stable Firebase uid, they accept by logging in via SMS OTP — the accept is still
135
+ auth-gated exactly as with email.
131
136
  - **Consent + suppression apply.** Transactional sends still respect the contact's
132
137
  channel consent and suppression list; a template's `topic` governs this.
133
138
  - **Delivery is logged** to comms history (with your `appId` and a `ref`), so sends
@@ -15,9 +15,20 @@ reserves states for them.
15
15
 
16
16
  | Mode | How | Who completes it |
17
17
  |------|-----|------------------|
18
- | **Directed** | Owner names a recipient (`toEmail` / `toUserId`). The proof is earmarked for them (`claimUserId`); you notify them with a comms trigger. | The named recipient calls `acceptTransfer`. |
18
+ | **Directed** | Owner names a recipient (`toEmail` / `toPhone` / `toUserId`). The proof is earmarked for them (`claimUserId`); you notify them with a comms trigger. | The named recipient calls `acceptTransfer`. |
19
19
  | **Open release** | Owner marks the proof `claimable`. | Anyone claims it via the normal claim flow. |
20
20
 
21
+ A `toPhone` recipient (E.164) resolves to the **same user they log in as via SMS
22
+ OTP** — Firebase keys the uid by phone number, so the earmark matches when they
23
+ sign in. Pair it with an SMS comms trigger to notify them by text:
24
+
25
+ ```ts
26
+ await proof.transfer(collectionId, productId, proofId, {
27
+ toPhone: '+14155551234',
28
+ comms: { recipient: { templateId: 'transfer-incoming', channel: 'sms' } },
29
+ })
30
+ ```
31
+
21
32
  Only the current owner (or a collection admin) can start a transfer. A proof can
22
33
  have **one active transfer at a time**.
23
34
 
package/dist/openapi.yaml CHANGED
@@ -10270,6 +10270,37 @@ paths:
10270
10270
  description: Unauthorized
10271
10271
  404:
10272
10272
  description: Not found
10273
+ post:
10274
+ tags:
10275
+ - attestations
10276
+ summary: "Create an OWNER-authored attestation (public write) — the counterpart to the admin {@link create}."
10277
+ operationId: attestations_publicCreate
10278
+ security: []
10279
+ parameters:
10280
+ - name: collectionId
10281
+ in: path
10282
+ required: true
10283
+ schema:
10284
+ type: string
10285
+ responses:
10286
+ 200:
10287
+ description: Success
10288
+ content:
10289
+ application/json:
10290
+ schema:
10291
+ $ref: "#/components/schemas/CreateOwnerAttestationResponse"
10292
+ 400:
10293
+ description: Bad request
10294
+ 401:
10295
+ description: Unauthorized
10296
+ 404:
10297
+ description: Not found
10298
+ requestBody:
10299
+ required: true
10300
+ content:
10301
+ application/json:
10302
+ schema:
10303
+ $ref: "#/components/schemas/OwnerAttestationInput"
10273
10304
  /public/collection/{collectionId}/attestations/latest:
10274
10305
  get:
10275
10306
  tags:
@@ -18782,6 +18813,39 @@ components:
18782
18813
  - subjectType
18783
18814
  - subjectId
18784
18815
  - attestationType
18816
+ OwnerAttestationInput:
18817
+ type: object
18818
+ properties:
18819
+ subjectType:
18820
+ $ref: "#/components/schemas/AttestationSubjectType"
18821
+ subjectId:
18822
+ type: string
18823
+ attestationType:
18824
+ type: string
18825
+ recordedAt:
18826
+ type: string
18827
+ visibility:
18828
+ type: string
18829
+ enum:
18830
+ - public
18831
+ - owner
18832
+ value:
18833
+ type: object
18834
+ additionalProperties: true
18835
+ ownerData:
18836
+ type: object
18837
+ additionalProperties: true
18838
+ unit:
18839
+ type: string
18840
+ source:
18841
+ type: string
18842
+ metadata:
18843
+ type: object
18844
+ additionalProperties: true
18845
+ required:
18846
+ - subjectType
18847
+ - subjectId
18848
+ - attestationType
18785
18849
  ListAttestationsResponse:
18786
18850
  type: object
18787
18851
  properties:
@@ -18791,6 +18855,13 @@ components:
18791
18855
  $ref: "#/components/schemas/Attestation"
18792
18856
  required:
18793
18857
  - attestations
18858
+ CreateOwnerAttestationResponse:
18859
+ type: object
18860
+ properties:
18861
+ attestation:
18862
+ $ref: "#/components/schemas/Attestation"
18863
+ required:
18864
+ - attestation
18794
18865
  PublicListAttestationsResponse:
18795
18866
  type: object
18796
18867
  properties:
@@ -25882,6 +25953,8 @@ components:
25882
25953
  $ref: "#/components/schemas/CommsTriggerMap"
25883
25954
  toEmail:
25884
25955
  type: string
25956
+ toPhone:
25957
+ type: string
25885
25958
  toUserId:
25886
25959
  type: string
25887
25960
  toName:
@@ -128,9 +128,31 @@ export interface CreateAttestationInput {
128
128
  authorId?: string;
129
129
  metadata?: Record<string, any>;
130
130
  }
131
+ /**
132
+ * Owner-authored attestation input (public write). The proof OWNER adds an
133
+ * attestation to their own item. Restricted vs {@link CreateAttestationInput}:
134
+ * no `adminData` (business-only zone), `visibility` limited to `'public' | 'owner'`,
135
+ * and `authorId` is forced to the caller by the server (so it's omitted here).
136
+ */
137
+ export interface OwnerAttestationInput {
138
+ subjectType: AttestationSubjectType;
139
+ subjectId: string;
140
+ attestationType: string;
141
+ recordedAt?: string;
142
+ /** `'public'` | `'owner'` only — defaults to `'owner'`. */
143
+ visibility?: 'public' | 'owner';
144
+ value?: Record<string, any>;
145
+ ownerData?: Record<string, any>;
146
+ unit?: string;
147
+ source?: string;
148
+ metadata?: Record<string, any>;
149
+ }
131
150
  export interface ListAttestationsResponse {
132
151
  attestations: Attestation[];
133
152
  }
153
+ export interface CreateOwnerAttestationResponse {
154
+ attestation: Attestation;
155
+ }
134
156
  export interface PublicListAttestationsResponse {
135
157
  attestations: Attestation[];
136
158
  /** Resolved audience tier; governs which data zones are populated */
@@ -280,17 +280,24 @@ export interface CommsTrigger {
280
280
  export type CommsTriggerMap = Record<string, CommsTrigger>;
281
281
  /**
282
282
  * Start a push transfer. Provide **one** of:
283
- * - `toEmail` / `toUserId` — a directed transfer to a named recipient (they accept).
283
+ * - `toEmail` / `toPhone` / `toUserId` — a directed transfer to a named recipient (they accept).
284
284
  * - `release: true` — an open release (the proof becomes claimable by anyone).
285
285
  */
286
286
  export interface TransferProofOptions {
287
287
  /**
288
288
  * Comms to send. Roles: `recipient` (the named new owner) and `sender` (the
289
- * initiator) for a directed transfer; `owner` for an open release.
289
+ * initiator) for a directed transfer; `owner` for an open release. For an
290
+ * SMS recipient, set `comms.recipient.channel = 'sms'`.
290
291
  */
291
292
  comms?: CommsTriggerMap;
292
293
  /** Directed: recipient email (created/looked up if needed). */
293
294
  toEmail?: string;
295
+ /**
296
+ * Directed: recipient phone in E.164 (e.g. `+14155551234`). Resolves to the same
297
+ * user they log in as via SMS OTP (created if needed). Pair with an SMS comms
298
+ * trigger to notify them: `comms.recipient.channel = 'sms'`.
299
+ */
300
+ toPhone?: string;
294
301
  /** Directed: recipient user id, if already known. */
295
302
  toUserId?: string;
296
303
  /** Directed: display name for a newly-created recipient. */
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.16.4 | Generated: 2026-09-05T14:13:52.478Z
3
+ Version: 1.16.6 | Generated: 2026-09-06T07:49:21.444Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -2797,6 +2797,22 @@ interface CreateAttestationInput {
2797
2797
  }
2798
2798
  ```
2799
2799
 
2800
+ **OwnerAttestationInput** (interface)
2801
+ ```typescript
2802
+ interface OwnerAttestationInput {
2803
+ subjectType: AttestationSubjectType
2804
+ subjectId: string
2805
+ attestationType: string
2806
+ recordedAt?: string
2807
+ visibility?: 'public' | 'owner'
2808
+ value?: Record<string, any>
2809
+ ownerData?: Record<string, any>
2810
+ unit?: string
2811
+ source?: string
2812
+ metadata?: Record<string, any>
2813
+ }
2814
+ ```
2815
+
2800
2816
  **ListAttestationsResponse** (interface)
2801
2817
  ```typescript
2802
2818
  interface ListAttestationsResponse {
@@ -2804,6 +2820,13 @@ interface ListAttestationsResponse {
2804
2820
  }
2805
2821
  ```
2806
2822
 
2823
+ **CreateOwnerAttestationResponse** (interface)
2824
+ ```typescript
2825
+ interface CreateOwnerAttestationResponse {
2826
+ attestation: Attestation
2827
+ }
2828
+ ```
2829
+
2807
2830
  **PublicListAttestationsResponse** (interface)
2808
2831
  ```typescript
2809
2832
  interface PublicListAttestationsResponse {
@@ -7605,9 +7628,14 @@ interface CommsTrigger {
7605
7628
  ```typescript
7606
7629
  interface TransferProofOptions {
7607
7630
  * Comms to send. Roles: `recipient` (the named new owner) and `sender` (the
7608
- * initiator) for a directed transfer; `owner` for an open release.
7631
+ * initiator) for a directed transfer; `owner` for an open release. For an
7632
+ * SMS recipient, set `comms.recipient.channel = 'sms'`.
7609
7633
  comms?: CommsTriggerMap
7610
7634
  toEmail?: string
7635
+ * Directed: recipient phone in E.164 (e.g. `+14155551234`). Resolves to the same
7636
+ * user they log in as via SMS OTP (created if needed). Pair with an SMS comms
7637
+ * trigger to notify them: `comms.recipient.channel = 'sms'`.
7638
+ toPhone?: string
7611
7639
  toUserId?: string
7612
7640
  toName?: string
7613
7641
  release?: boolean
@@ -9058,6 +9086,10 @@ Tree latest snapshot — most-recent record per type across a container subtree
9058
9086
  params: ListAttestationsParams) → `Promise<PublicListAttestationsResponse>`
9059
9087
  List attestations for a subject (public). Records with `visibility='admin'` are always excluded. Records with `visibility='owner'` are included only when the caller provides a valid Firebase ID token that resolves to the subject owner. The `audience` field in the response indicates the tier that was served. ```typescript const { attestations: records, audience } = await attestations.publicList('coll_123', { subjectType: 'proof', subjectId: 'proof-uuid', }) ```
9060
9088
 
9089
+ **publicCreate**(collectionId: string,
9090
+ 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', }) ```
9092
+
9061
9093
  **publicSummary**(collectionId: string,
9062
9094
  params: AttestationSummaryParams) → `Promise<PublicAttestationSummaryResponse>`
9063
9095
  Time-series summary (public). Always served at `audience='public'`. Same parameters as the admin version. `attestationType` are required
@@ -10470,7 +10502,7 @@ Redeem a grant token (anonymous or signed-in). Records the redemption and return
10470
10502
  productId: string,
10471
10503
  proofId: string,
10472
10504
  options: TransferProofOptions) → `Promise<TransferProofResult>`
10473
- Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ``` Send comms by naming templates per role (server decides who receives each): ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com', comms: { recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } }, sender: { templateId: 'transfer-sent' }, }, }) ```
10505
+ Start a push transfer of a proof (current owner / collection admin only). Directed — hand it to a named recipient who then calls {@link acceptTransfer}: ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com' }) ``` Open release — make the proof claimable by anyone: ```ts await proof.transfer(collectionId, productId, proofId, { release: true }) ``` Send comms by naming templates per role (server decides who receives each): ```ts await proof.transfer(collectionId, productId, proofId, { toEmail: 'buyer@example.com', comms: { recipient: { templateId: 'transfer-incoming', props: { note: 'Enjoy!' } }, sender: { templateId: 'transfer-sent' }, }, }) ``` Direct to an SMS recipient by phone (E.164) and notify them by text: ```ts await proof.transfer(collectionId, productId, proofId, { toPhone: '+14155551234', comms: { recipient: { templateId: 'transfer-incoming', channel: 'sms' } }, }) ```
10474
10506
 
10475
10507
  **acceptTransfer**(collectionId: string,
10476
10508
  productId: string,
@@ -159,6 +159,15 @@ The middleware resolves the UID and checks ownership:
159
159
 
160
160
  When ownership is confirmed the request is served at `audience='owner'`, which includes `ownerData`.
161
161
 
162
+ **Owner write** — the proof owner may also *author* an attestation on their own item via `POST /api/v1/public/collection/:collectionId/attestations` (`attestations.publicCreate`). It is the public counterpart to the admin write, with tight guardrails:
163
+
164
+ - the caller must be authenticated and **own the linked proof** (identity — a read grant is not enough); a subject with no linked proof is rejected;
165
+ - they may write `value` and `ownerData` only — **`adminData` is dropped** (the business zone);
166
+ - `visibility` is clamped to `'public' | 'owner'` (never `'admin'`), defaulting to `'owner'`;
167
+ - `authorId` is forced to the caller and `metadata.authorType = 'owner'` is stamped.
168
+
169
+ The record joins the same append-only, hash-chained log as business/system writes. Business writes (any zone/visibility) remain admin-only.
170
+
162
171
  ### Visibility vs audience
163
172
 
164
173
  `visibility` is a property of an **individual record** set at write time. `audience` describes the **caller's tier** resolved at read time. The server applies:
@@ -128,6 +128,11 @@ Omit `comms` entirely and nothing is sent — comms are **opt-in**.
128
128
 
129
129
  - **Email-only recipients work.** A directed transfer to a `toEmail` that isn't a
130
130
  contact yet resolves/creates a contact before sending.
131
+ - **SMS recipients work the same way.** A directed transfer to a `toPhone` (E.164)
132
+ resolves the recipient by phone and gives their contact a phone identity, so an
133
+ SMS-channel trigger (`channel: 'sms'`) reaches them. Because the phone maps to a
134
+ stable Firebase uid, they accept by logging in via SMS OTP — the accept is still
135
+ auth-gated exactly as with email.
131
136
  - **Consent + suppression apply.** Transactional sends still respect the contact's
132
137
  channel consent and suppression list; a template's `topic` governs this.
133
138
  - **Delivery is logged** to comms history (with your `appId` and a `ref`), so sends
@@ -15,9 +15,20 @@ reserves states for them.
15
15
 
16
16
  | Mode | How | Who completes it |
17
17
  |------|-----|------------------|
18
- | **Directed** | Owner names a recipient (`toEmail` / `toUserId`). The proof is earmarked for them (`claimUserId`); you notify them with a comms trigger. | The named recipient calls `acceptTransfer`. |
18
+ | **Directed** | Owner names a recipient (`toEmail` / `toPhone` / `toUserId`). The proof is earmarked for them (`claimUserId`); you notify them with a comms trigger. | The named recipient calls `acceptTransfer`. |
19
19
  | **Open release** | Owner marks the proof `claimable`. | Anyone claims it via the normal claim flow. |
20
20
 
21
+ A `toPhone` recipient (E.164) resolves to the **same user they log in as via SMS
22
+ OTP** — Firebase keys the uid by phone number, so the earmark matches when they
23
+ sign in. Pair it with an SMS comms trigger to notify them by text:
24
+
25
+ ```ts
26
+ await proof.transfer(collectionId, productId, proofId, {
27
+ toPhone: '+14155551234',
28
+ comms: { recipient: { templateId: 'transfer-incoming', channel: 'sms' } },
29
+ })
30
+ ```
31
+
21
32
  Only the current owner (or a collection admin) can start a transfer. A proof can
22
33
  have **one active transfer at a time**.
23
34
 
package/openapi.yaml CHANGED
@@ -10270,6 +10270,37 @@ paths:
10270
10270
  description: Unauthorized
10271
10271
  404:
10272
10272
  description: Not found
10273
+ post:
10274
+ tags:
10275
+ - attestations
10276
+ summary: "Create an OWNER-authored attestation (public write) — the counterpart to the admin {@link create}."
10277
+ operationId: attestations_publicCreate
10278
+ security: []
10279
+ parameters:
10280
+ - name: collectionId
10281
+ in: path
10282
+ required: true
10283
+ schema:
10284
+ type: string
10285
+ responses:
10286
+ 200:
10287
+ description: Success
10288
+ content:
10289
+ application/json:
10290
+ schema:
10291
+ $ref: "#/components/schemas/CreateOwnerAttestationResponse"
10292
+ 400:
10293
+ description: Bad request
10294
+ 401:
10295
+ description: Unauthorized
10296
+ 404:
10297
+ description: Not found
10298
+ requestBody:
10299
+ required: true
10300
+ content:
10301
+ application/json:
10302
+ schema:
10303
+ $ref: "#/components/schemas/OwnerAttestationInput"
10273
10304
  /public/collection/{collectionId}/attestations/latest:
10274
10305
  get:
10275
10306
  tags:
@@ -18782,6 +18813,39 @@ components:
18782
18813
  - subjectType
18783
18814
  - subjectId
18784
18815
  - attestationType
18816
+ OwnerAttestationInput:
18817
+ type: object
18818
+ properties:
18819
+ subjectType:
18820
+ $ref: "#/components/schemas/AttestationSubjectType"
18821
+ subjectId:
18822
+ type: string
18823
+ attestationType:
18824
+ type: string
18825
+ recordedAt:
18826
+ type: string
18827
+ visibility:
18828
+ type: string
18829
+ enum:
18830
+ - public
18831
+ - owner
18832
+ value:
18833
+ type: object
18834
+ additionalProperties: true
18835
+ ownerData:
18836
+ type: object
18837
+ additionalProperties: true
18838
+ unit:
18839
+ type: string
18840
+ source:
18841
+ type: string
18842
+ metadata:
18843
+ type: object
18844
+ additionalProperties: true
18845
+ required:
18846
+ - subjectType
18847
+ - subjectId
18848
+ - attestationType
18785
18849
  ListAttestationsResponse:
18786
18850
  type: object
18787
18851
  properties:
@@ -18791,6 +18855,13 @@ components:
18791
18855
  $ref: "#/components/schemas/Attestation"
18792
18856
  required:
18793
18857
  - attestations
18858
+ CreateOwnerAttestationResponse:
18859
+ type: object
18860
+ properties:
18861
+ attestation:
18862
+ $ref: "#/components/schemas/Attestation"
18863
+ required:
18864
+ - attestation
18794
18865
  PublicListAttestationsResponse:
18795
18866
  type: object
18796
18867
  properties:
@@ -25882,6 +25953,8 @@ components:
25882
25953
  $ref: "#/components/schemas/CommsTriggerMap"
25883
25954
  toEmail:
25884
25955
  type: string
25956
+ toPhone:
25957
+ type: string
25885
25958
  toUserId:
25886
25959
  type: string
25887
25960
  toName:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.16.4",
3
+ "version": "1.16.6",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",