@proveanything/smartlinks 2.0.70 → 2.1.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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 2.0.70 | Generated: 2026-10-24T08:16:51.896Z
3
+ Version: 2.1.0 | Generated: 2026-10-24T18:38:40.481Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -140,6 +140,7 @@ The Smartlinks SDK is organized into the following namespaces:
140
140
  - **async** - Functions for async operations
141
141
  - **attestation** - Functions for attestation operations
142
142
  - **attestations** - Functions for attestations operations
143
+ - **bookings** - Functions for bookings operations
143
144
  - **config** - Functions for config operations
144
145
  - **containers** - Functions for containers operations
145
146
  - **facets** - Functions for facets operations
@@ -2800,9 +2801,41 @@ interface ServerFunctionInteractionEvent {
2800
2801
  }
2801
2802
  ```
2802
2803
 
2804
+ **ServerFunctionBookings** (interface)
2805
+ ```typescript
2806
+ interface ServerFunctionBookings {
2807
+ create(input: CreateBookingInput): Promise<Booking & { replayed: boolean }>;
2808
+ get(bookingId: string): Promise<Booking | null>;
2809
+ query(params?: BookingListQueryParams): Promise<PaginatedResponse<Booking>>;
2810
+ mine(params?: BookingListQueryParams): Promise<PaginatedResponse<Booking>>;
2811
+ update(bookingId: string, fields: UpdateBookingInput): Promise<Booking>;
2812
+ confirm(bookingId: string): Promise<Booking>;
2813
+ cancel(bookingId: string, opts?: { reason?: string }): Promise<Booking>;
2814
+ move(bookingId: string, input: MoveBookingInput): Promise<Booking>;
2815
+ extendHold(bookingId: string, holdMinutes: number): Promise<Booking>;
2816
+ complete(bookingId: string): Promise<Booking>;
2817
+ noShow(bookingId: string): Promise<Booking>;
2818
+ freebusy(q: FreeBusyQuery): Promise<FreeBusyResponse>;
2819
+ slots: {
2820
+ create(input: CreateSlotInput): Promise<BookingSlot>;
2821
+ createMany(inputs: CreateSlotInput[], opts?: { seriesId?: string }): Promise<BookingSlot[]>;
2822
+ get(slotId: string): Promise<BookingSlot | null>;
2823
+ query(params?: SlotListQueryParams): Promise<PaginatedResponse<BookingSlot>>;
2824
+ update(slotId: string, fields: UpdateSlotInput): Promise<BookingSlot>;
2825
+ delete(slotId: string): Promise<{ success: boolean }>;
2826
+ };
2827
+ blocks: {
2828
+ create(input: CreateBlockInput): Promise<BookingAllocation>;
2829
+ query(params?: BlockListQueryParams): Promise<PaginatedResponse<BookingAllocation>>;
2830
+ delete(blockId: string): Promise<{ success: boolean }>;
2831
+ };
2832
+ }
2833
+ ```
2834
+
2803
2835
  **ServerFunctionSl** (interface)
2804
2836
  ```typescript
2805
2837
  interface ServerFunctionSl {
2838
+ bookings: ServerFunctionBookings;
2806
2839
  appRecords: { claim(fields: Record<string, any>): Promise<AppRecordClaimResult>; [method: string]: any };
2807
2840
  counters: ServerFunctionCounters;
2808
2841
  tags: { resolve(id: string): Promise<ResolvedTag | null> };
@@ -3817,6 +3850,7 @@ interface Asset {
3817
3850
  proofId: string | null
3818
3851
  appId: string | null
3819
3852
  url: string
3853
+ assetUrl: string | null
3820
3854
  thumbnail: string | null
3821
3855
  name: string
3822
3856
  cleanName: string | null
@@ -4054,6 +4088,7 @@ interface CreateResumableUploadOptions {
4054
4088
  appId?: string
4055
4089
  admin?: boolean
4056
4090
  token?: string
4091
+ replaceAssetId?: string
4057
4092
  }
4058
4093
  ```
4059
4094
 
@@ -5065,6 +5100,329 @@ interface BatchTag {
5065
5100
  }
5066
5101
  ```
5067
5102
 
5103
+ ### bookings
5104
+
5105
+ **BookingAllocation** (interface)
5106
+ ```typescript
5107
+ interface BookingAllocation {
5108
+ id: string
5109
+ bookingId: string | null
5110
+ slotId: string | null
5111
+ resourceType: string
5112
+ resourceId: string
5113
+ startsAt: string
5114
+ endsAt: string
5115
+ quantity: number
5116
+ mode: 'exclusive' | 'capacity'
5117
+ active: boolean
5118
+ sourceSystem?: string | null
5119
+ data: Record<string, any>
5120
+ }
5121
+ ```
5122
+
5123
+ **Booking** (interface)
5124
+ ```typescript
5125
+ interface Booking {
5126
+ id: string
5127
+ collectionId: string
5128
+ appId: string
5129
+ visibility: BookingVisibility
5130
+ contactId?: string
5131
+ status: BookingStatus
5132
+ startsAt: string
5133
+ endsAt: string
5134
+ timezone?: string | null
5135
+ quantity: number
5136
+ holdExpiresAt?: string | null
5137
+ flags: string[]
5138
+ cancelReason?: string | null
5139
+ idempotencyKey?: string | null
5140
+ seriesId?: string | null
5141
+ ref?: string | null
5142
+ customId?: string | null
5143
+ sourceSystem?: string | null
5144
+ productId?: string | null
5145
+ proofId?: string
5146
+ parentType?: string | null
5147
+ parentId?: string | null
5148
+ createdAt: string
5149
+ updatedAt: string
5150
+ cancelledAt?: string | null
5151
+ data: Record<string, any>
5152
+ owner?: Record<string, any>
5153
+ admin?: Record<string, any>
5154
+ allocations: BookingAllocation[]
5155
+ editToken?: string
5156
+ }
5157
+ ```
5158
+
5159
+ **BookingSlot** (interface)
5160
+ ```typescript
5161
+ interface BookingSlot {
5162
+ id: string
5163
+ collectionId: string
5164
+ appId: string
5165
+ visibility: BookingVisibility
5166
+ resourceType?: string | null
5167
+ resourceId?: string | null
5168
+ startsAt: string
5169
+ endsAt: string
5170
+ timezone?: string | null
5171
+ capacity: number
5172
+ booked: number
5173
+ remaining: number
5174
+ perContactLimit?: number | null
5175
+ status: 'open' | 'closed' | 'cancelled'
5176
+ seriesId?: string | null
5177
+ ref?: string | null
5178
+ customId?: string | null
5179
+ sourceSystem?: string | null
5180
+ productId?: string | null
5181
+ createdAt: string
5182
+ updatedAt: string
5183
+ data: Record<string, any>
5184
+ owner?: Record<string, any>
5185
+ admin?: Record<string, any>
5186
+ }
5187
+ ```
5188
+
5189
+ **CreateBookingInput** (interface)
5190
+ ```typescript
5191
+ interface CreateBookingInput {
5192
+ slotId?: string
5193
+ quantity?: number
5194
+ resourceType?: string
5195
+ resourceId?: string
5196
+ resource?: { type: string; id: string }
5197
+ startsAt?: string | Date
5198
+ endsAt?: string | Date
5199
+ allocations?: BookingClaimInput[]
5200
+ status?: 'held' | 'pending' | 'confirmed' | 'waitlisted'
5201
+ holdMinutes?: number
5202
+ bufferBefore?: number
5203
+ bufferAfter?: number
5204
+ idempotencyKey?: string
5205
+ contactId?: string
5206
+ timezone?: string
5207
+ visibility?: BookingVisibility
5208
+ ref?: string
5209
+ customId?: string
5210
+ sourceSystem?: string
5211
+ productId?: string
5212
+ proofId?: string
5213
+ parentType?: string
5214
+ parentId?: string
5215
+ seriesId?: string
5216
+ data?: Record<string, any>
5217
+ owner?: Record<string, any>
5218
+ admin?: Record<string, any>
5219
+ }
5220
+ ```
5221
+
5222
+ **UpdateBookingInput** (interface)
5223
+ ```typescript
5224
+ interface UpdateBookingInput {
5225
+ data?: Record<string, any>
5226
+ owner?: Record<string, any>
5227
+ admin?: Record<string, any>
5228
+ visibility?: BookingVisibility
5229
+ ref?: string
5230
+ timezone?: string
5231
+ productId?: string
5232
+ proofId?: string
5233
+ contactId?: string
5234
+ sourceSystem?: string
5235
+ customId?: string
5236
+ flags?: string[]
5237
+ }
5238
+ ```
5239
+
5240
+ **MoveBookingInput** (interface)
5241
+ ```typescript
5242
+ interface MoveBookingInput {
5243
+ slotId?: string
5244
+ resourceType?: string
5245
+ resourceId?: string
5246
+ resource?: { type: string; id: string }
5247
+ startsAt?: string | Date
5248
+ endsAt?: string | Date
5249
+ allocations?: BookingClaimInput[]
5250
+ bufferBefore?: number
5251
+ bufferAfter?: number
5252
+ }
5253
+ ```
5254
+
5255
+ **BookingListQueryParams** (interface)
5256
+ ```typescript
5257
+ interface BookingListQueryParams {
5258
+ status?: string
5259
+ contactId?: string
5260
+ slotId?: string
5261
+ resourceType?: string
5262
+ resourceId?: string
5263
+ from?: string | Date
5264
+ to?: string | Date
5265
+ ref?: string
5266
+ customId?: string
5267
+ sourceSystem?: string
5268
+ productId?: string
5269
+ seriesId?: string
5270
+ sort?: string
5271
+ limit?: number
5272
+ offset?: number
5273
+ }
5274
+ ```
5275
+
5276
+ **CreateSlotInput** (interface)
5277
+ ```typescript
5278
+ interface CreateSlotInput {
5279
+ startsAt: string | Date
5280
+ endsAt: string | Date
5281
+ capacity: number
5282
+ perContactLimit?: number
5283
+ resourceType?: string
5284
+ resourceId?: string
5285
+ blockResource?: boolean
5286
+ status?: 'open' | 'closed' | 'cancelled'
5287
+ timezone?: string
5288
+ visibility?: BookingVisibility
5289
+ seriesId?: string
5290
+ ref?: string
5291
+ customId?: string
5292
+ sourceSystem?: string
5293
+ productId?: string
5294
+ data?: Record<string, any>
5295
+ owner?: Record<string, any>
5296
+ admin?: Record<string, any>
5297
+ }
5298
+ ```
5299
+
5300
+ **SlotListQueryParams** (interface)
5301
+ ```typescript
5302
+ interface SlotListQueryParams {
5303
+ status?: string
5304
+ available?: boolean
5305
+ from?: string | Date
5306
+ to?: string | Date
5307
+ resourceType?: string
5308
+ resourceId?: string
5309
+ productId?: string
5310
+ seriesId?: string
5311
+ ref?: string
5312
+ customId?: string
5313
+ sort?: string
5314
+ limit?: number
5315
+ offset?: number
5316
+ }
5317
+ ```
5318
+
5319
+ **CreateBlockInput** (interface)
5320
+ ```typescript
5321
+ interface CreateBlockInput {
5322
+ resourceType: string
5323
+ resourceId: string
5324
+ startsAt: string | Date
5325
+ endsAt: string | Date
5326
+ sourceSystem?: string
5327
+ data?: Record<string, any>
5328
+ }
5329
+ ```
5330
+
5331
+ **BlockListQueryParams** (interface)
5332
+ ```typescript
5333
+ interface BlockListQueryParams {
5334
+ resourceType?: string
5335
+ resourceId?: string
5336
+ from?: string | Date
5337
+ to?: string | Date
5338
+ limit?: number
5339
+ offset?: number
5340
+ }
5341
+ ```
5342
+
5343
+ **FreeBusyQuery** (interface)
5344
+ ```typescript
5345
+ interface FreeBusyQuery {
5346
+ resources: BookingResourceRef[]
5347
+ from: string | Date
5348
+ to: string | Date
5349
+ }
5350
+ ```
5351
+
5352
+ **FreeBusyInterval** (interface)
5353
+ ```typescript
5354
+ interface FreeBusyInterval {
5355
+ start: string
5356
+ end: string
5357
+ }
5358
+ ```
5359
+
5360
+ **FreeBusyResponse** (interface)
5361
+ ```typescript
5362
+ interface FreeBusyResponse {
5363
+ from: string
5364
+ to: string
5365
+ resources: Array<{ resourceType: string; resourceId: string; busy: FreeBusyInterval[] }>
5366
+ }
5367
+ ```
5368
+
5369
+ **BookingAccessOptions** (interface)
5370
+ ```typescript
5371
+ interface BookingAccessOptions {
5372
+ editToken?: string
5373
+ }
5374
+ ```
5375
+
5376
+ **ScheduleHours** (interface)
5377
+ ```typescript
5378
+ interface ScheduleHours {
5379
+ start: string
5380
+ end: string
5381
+ }
5382
+ ```
5383
+
5384
+ **ScheduleOverride** (interface)
5385
+ ```typescript
5386
+ interface ScheduleOverride {
5387
+ date?: string
5388
+ from?: string
5389
+ to?: string
5390
+ hours: ScheduleHours[]
5391
+ }
5392
+ ```
5393
+
5394
+ **StandardSchedule** (interface)
5395
+ ```typescript
5396
+ interface StandardSchedule {
5397
+ timezone: string
5398
+ weekly?: Partial<Record<ScheduleDay, ScheduleHours[]>>
5399
+ overrides?: ScheduleOverride[]
5400
+ }
5401
+ ```
5402
+
5403
+ **BookingStatus** = ``
5404
+
5405
+ **BookingVisibility** = `'public' | 'owner' | 'admin'`
5406
+
5407
+ **BookingClaimInput** = ``
5408
+
5409
+ **UpdateSlotInput** = `Partial<Omit<CreateSlotInput, 'blockResource' | 'seriesId' | 'resourceType' | 'resourceId'>>`
5410
+
5411
+ **BookingResourceRef** (type)
5412
+ ```typescript
5413
+ type BookingResourceRef = { type: string; id: string }
5414
+ ```
5415
+
5416
+ **BookingList** = `PaginatedResponse<Booking>`
5417
+
5418
+ **SlotList** = `PaginatedResponse<BookingSlot>`
5419
+
5420
+ **BlockList** = `PaginatedResponse<BookingAllocation>`
5421
+
5422
+ **BookingErrorCode** = ``
5423
+
5424
+ **ScheduleDay** = `'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun'`
5425
+
5068
5426
  ### broadcasts
5069
5427
 
5070
5428
  **ListBroadcastsQuery** (interface)
@@ -10568,6 +10926,41 @@ interface ConditionDebugOptions {
10568
10926
 
10569
10927
  **ConditionDebugLogger** = `(...args: any[]) => void`
10570
10928
 
10929
+ ### freeTimes (utils)
10930
+
10931
+ **TimeInterval** (interface)
10932
+ ```typescript
10933
+ interface TimeInterval {
10934
+ start: number
10935
+ end: number
10936
+ }
10937
+ ```
10938
+
10939
+ **FreeTimesOptions** (interface)
10940
+ ```typescript
10941
+ interface FreeTimesOptions {
10942
+ hours: StandardSchedule | TimeInterval[]
10943
+ busy?: Array<FreeBusyInterval | TimeInterval>
10944
+ from: string | number | Date
10945
+ to: string | number | Date
10946
+ duration: number
10947
+ step?: number
10948
+ bufferBefore?: number
10949
+ bufferAfter?: number
10950
+ minNotice?: number
10951
+ now?: number
10952
+ buffersWithinHours?: boolean
10953
+ }
10954
+ ```
10955
+
10956
+ **FreeTimeSlot** (interface)
10957
+ ```typescript
10958
+ interface FreeTimeSlot {
10959
+ start: string
10960
+ end: string
10961
+ }
10962
+ ```
10963
+
10571
10964
  ### paths (utils)
10572
10965
 
10573
10966
  **PortalPathParams** (interface)
@@ -11065,6 +11458,9 @@ Soft-delete an asset. Schedules CDN purge after `graceDays` (default 30). Recove
11065
11458
  **restoreAdmin**(collectionId: string, assetId: string) → `Promise<Asset>`
11066
11459
  Restore a soft-deleted asset (clears `deletedAt`).
11067
11460
 
11461
+ **ensureAssetUrl**(collectionId: string, assetId: string) → `Promise<Asset>`
11462
+ Give an existing asset its own address (`assetUrl`). New and replaced assets get one automatically; this is for assets made before asset addresses existed. Safe to call again. → the asset, with `assetUrl`.
11463
+
11068
11464
  **bulkDelete**(options: BulkDeleteAssetsOptions) → `Promise<`
11069
11465
  Soft-delete multiple assets in one request.
11070
11466
 
@@ -11468,6 +11864,72 @@ Find a specific batch by ID across all products in a collection. Returns the bat
11468
11864
  claimSetId?: string) → `Promise<BatchTag[]>`
11469
11865
  Get all tags/codes assigned to a specific batch. Shows which claim set codes have been assigned to this batch. ```typescript // Get all tags assigned to a batch const tags = await batch.getBatchTags('coll_123', 'BATCH-2024-001') console.log(`Batch has ${tags.length} tags assigned`) tags.forEach(tag => { console.log(`Code: ${tag.code}, ClaimSet: ${tag.claimSetId}, TagID: ${tag.tagId}`) }) // Get tags from a specific claim set const claimSetTags = await batch.getBatchTags('coll_123', 'BATCH-2024-001', '000001') ```
11470
11866
 
11867
+ ### bookings
11868
+
11869
+ **create**(collectionId: string, appId: string, input: CreateBookingInput, admin: boolean = false) → `Promise<Booking>`
11870
+ Book. All claims succeed together or nothing is written; a clash fails with 409 (`SLOT_FULL`, `RESOURCE_TAKEN`, `PER_CONTACT_LIMIT`, …). Pass `idempotencyKey` so a retry never books twice. Public (admin = false): the app's `publicCreate.bookings` rules decide the status and limits; an anonymous guest may get `editToken` back (once) to manage the booking later. POST /bookings
11871
+
11872
+ **list**(collectionId: string, appId: string, params?: BookingListQueryParams, admin: boolean = true) → `Promise<PaginatedResponse<Booking>>`
11873
+ List bookings (admin). GET /bookings
11874
+
11875
+ **mine**(collectionId: string, appId: string, params?: BookingListQueryParams) → `Promise<PaginatedResponse<Booking>>`
11876
+ The signed-in caller's own bookings (public). GET /bookings/mine
11877
+
11878
+ **get**(collectionId: string, appId: string, bookingId: string, admin: boolean = false, opts?: BookingAccessOptions) → `Promise<Booking>`
11879
+ Get one booking. Public: the signed-in booker, or a guest passing `{ editToken }`. GET /bookings/:bookingId
11880
+
11881
+ **update**(collectionId: string, appId: string, bookingId: string, input: UpdateBookingInput, admin: boolean = true) → `Promise<Booking>`
11882
+ Update zones and descriptive fields (admin). PATCH /bookings/:bookingId
11883
+
11884
+ **confirm**(collectionId: string, appId: string, bookingId: string, admin: boolean = false, opts?: BookingAccessOptions) → `Promise<Booking>`
11885
+ held / pending / waitlisted → confirmed (idempotent). A held booking whose hold has run out fails with HOLD_EXPIRED. Public: only the booker's own hold, when `ownerActions.bookings.confirm` allows. POST /bookings/:bookingId/confirm
11886
+
11887
+ **cancel**(collectionId: string, appId: string, bookingId: string, admin: boolean = false, opts?: BookingAccessOptions & { reason?: string }) → `Promise<Booking>`
11888
+ Cancel and give the places back (idempotent). Public: per `ownerActions.bookings.cancel` and its cutoff. `reason` is admin-only (public cancels record 'owner'). POST /bookings/:bookingId/cancel
11889
+
11890
+ **move**(collectionId: string, appId: string, bookingId: string, input: MoveBookingInput, admin: boolean = false, opts?: BookingAccessOptions) → `Promise<Booking>`
11891
+ Move to a new slot / time, all-or-nothing (a failed move leaves the booking as it was). Public: per `ownerActions.bookings.move`; the create rules are re-checked for the new time. POST /bookings/:bookingId/move
11892
+
11893
+ **extendHold**(collectionId: string, appId: string, bookingId: string, holdMinutes: number) → `Promise<Booking>`
11894
+ Extend a hold by `holdMinutes` from now (admin; capped at 24h after the booking was made).
11895
+
11896
+ **complete**(collectionId: string, appId: string, bookingId: string) → `Promise<Booking>`
11897
+ confirmed → completed (admin).
11898
+
11899
+ **noShow**(collectionId: string, appId: string, bookingId: string) → `Promise<Booking>`
11900
+ confirmed → no_show (admin).
11901
+
11902
+ **freebusy**(collectionId: string, appId: string, q: FreeBusyQuery, admin: boolean = false) → `Promise<FreeBusyResponse>`
11903
+ Busy intervals per resource (bookings, blocks, slots' own blocks) — never booking details. Public only when the app sets `publicRead.bookings.freebusy: true`. GET /bookings/freebusy
11904
+
11905
+ ### bookings.blocks
11906
+
11907
+ **create**(collectionId: string, appId: string, input: CreateBlockInput) → `Promise<BookingAllocation>`
11908
+
11909
+ **list**(collectionId: string, appId: string, params?: BlockListQueryParams) → `Promise<PaginatedResponse<BookingAllocation>>`
11910
+
11911
+ **remove**(collectionId: string, appId: string, blockId: string) → `Promise<`
11912
+
11913
+ ### bookings.slots
11914
+
11915
+ **create**(collectionId: string, appId: string, input: CreateSlotInput) → `Promise<BookingSlot>`
11916
+ Create a slot (admin). POST /bookings/slots
11917
+
11918
+ **createMany**(collectionId: string, appId: string, slots: CreateSlotInput[], opts?: { seriesId?: string }) → `Promise<`
11919
+ Create many slots at once — a series — all-or-nothing (admin, max 500). POST /bookings/slots/batch
11920
+
11921
+ **list**(collectionId: string, appId: string, params?: SlotListQueryParams, admin: boolean = false) → `Promise<PaginatedResponse<BookingSlot>>`
11922
+ List slots. Public: open, public slots only, each with `remaining`. GET /bookings/slots
11923
+
11924
+ **get**(collectionId: string, appId: string, slotId: string, admin: boolean = false) → `Promise<BookingSlot>`
11925
+ GET /bookings/slots/:slotId
11926
+
11927
+ **update**(collectionId: string, appId: string, slotId: string, input: UpdateSlotInput) → `Promise<BookingSlot>`
11928
+ Update a slot (admin). Capacity can't drop below what's booked (SLOT_OVERBOOKED); times can't change once anything is booked (SLOT_HAS_BOOKINGS).
11929
+
11930
+ **remove**(collectionId: string, appId: string, slotId: string) → `Promise<`
11931
+ Delete a slot (admin; refused while places are booked).
11932
+
11471
11933
  ### broadcasts
11472
11934
 
11473
11935
  **create**(collectionId: string,
@@ -12,6 +12,10 @@ SmartLinks provides three generic data models scoped to your app that can be ada
12
12
  - **Threads** — Manage discussions, comments, or any reply-based content
13
13
  - **Records** — Store structured data with flexible lifecycles
14
14
 
15
+ > **Bookings** (anything that claims time or a place — events, classes, appointments, rooms) have
16
+ > their own objects with no-double-booking and capacity guarantees: see [bookings.md](bookings.md).
17
+ > A booking app still uses records for its schedules, staff and settings.
18
+
15
19
  Each object type supports:
16
20
  - **JSONB zones** (`data`, `owner`, `admin`) for granular access control
17
21
  - **Visibility levels** (`public`, `owner`, `admin`) for content exposure
@@ -923,6 +927,9 @@ The server reads this document at request time — no cache invalidation or serv
923
927
 
924
928
  ### Policy Structure
925
929
 
930
+ Bookings use the same policy as a fourth object type, `publicCreate.bookings`, with booking-specific
931
+ `limits` and `rules` — see [bookings.md](bookings.md).
932
+
926
933
  Each object type (`cases`, `threads`, `records`) has **independent branches** for anonymous and authenticated callers. Each branch carries its own `allow` flag, optional field overrides (`enforce`), and — for records — optional edit-token config (`edit`).
927
934
 
928
935
  ```typescript
@@ -2,6 +2,10 @@
2
2
 
3
3
  Reference for asset types, endpoints, and public (token-based) uploads.
4
4
 
5
+ > **Showing an asset in a page, email or other static content? Use `assetUrl`.** It's the asset's own
6
+ > address and always shows its current file, so replacing the file needs no code change or redeploy. See
7
+ > [Asset addresses: `assetUrl` vs `url`](#asset-addresses-asseturl-vs-url).
8
+
5
9
  ---
6
10
 
7
11
  ## Asset object
@@ -17,7 +21,9 @@ interface Asset {
17
21
  appId: string | null // app that owns this asset, e.g. 'homepage'
18
22
 
19
23
  // File
20
- url: string // CDN URL of the original file
24
+ url: string // address of the current FILE — versioned, changes when the file is replaced
25
+ assetUrl: string | null // the ASSET's own address — always its current file; use in static content
26
+ // (see "Asset addresses" below)
21
27
  thumbnail: string | null // CDN URL of WebP thumbnail (max 512px longest edge, no crop)
22
28
  // Always .webp — null until thumbnail generation has run
23
29
  name: string // original filename
@@ -84,6 +90,12 @@ interface AssetRef {
84
90
  }
85
91
  ```
86
92
 
93
+ An `AssetRef` has no `assetUrl`, on purpose. A product's image is part of the product record: to change it,
94
+ point the record at a new asset (upload, then save the product). That's a record change, so the product's
95
+ change event fires and integrations and syndication pick it up. Retailers cache images by URL, so the new
96
+ address is what makes them fetch the new image. Show product images from the record, never copied into
97
+ static HTML.
98
+
87
99
  Render the thumbnail wherever a compact preview is needed; fall back to `url` if null:
88
100
 
89
101
  ```typescript
@@ -185,7 +197,10 @@ await Api.asset.updateAdmin({
185
197
  POST /asset/:assetId/replace
186
198
  ```
187
199
 
188
- Replaces the file; the previous URL is snapshotted into `versions[]`.
200
+ Replaces the file; the previous URL is snapshotted into `versions[]`. The id and `assetUrl` stay the same,
201
+ and `assetUrl` serves the new file straight away. `url` becomes the new file's address. See
202
+ [Asset addresses](#asset-addresses-asseturl-vs-url). For a large file, use a resumable upload with
203
+ `replaceAssetId`.
189
204
 
190
205
  ```typescript
191
206
  await Api.asset.replaceFile({
@@ -304,6 +319,7 @@ interface CreateResumableUploadOptions {
304
319
  appId?: string
305
320
  admin?: boolean // admin route (default is the public route)
306
321
  token?: string // upload token for public/unauthenticated uploads
322
+ replaceAssetId?: string // admin: replace this asset's file (any size) instead of creating a new asset
307
323
  }
308
324
 
309
325
  interface ResumableUploadHandle {
@@ -321,6 +337,51 @@ interface ResumableUploadHandle {
321
337
  - On completion, `start()`/`resume()` resolves to the finalized `Asset` record.
322
338
  - `pause()` causes the in-flight `start()`/`resume()` promise to reject with an `UploadPausedError`; call `resume()` to continue.
323
339
  - `asset.upload()` also now accepts an `AbortSignal` (`upload({ ..., signal })`) for cancelling a stalled single-shot upload.
340
+ - With `replaceAssetId`, the completed upload becomes a new version of that asset (same id and `assetUrl`), so a
341
+ 200 MB video can be swapped without touching the pages that show it.
342
+
343
+ ---
344
+
345
+ ## Asset addresses: `assetUrl` vs `url`
346
+
347
+ An asset is a named thing in the library, such as "Hero image". Its file can be replaced over time, so it has
348
+ two addresses:
349
+
350
+ | Field | Is the address of | When the file is replaced |
351
+ |---|---|---|
352
+ | `assetUrl` | the **asset**: `https://cdn.smartlinks.app/sites/{collectionId}/a/{assetId}` | stays the same, and shows the new file |
353
+ | `url` | the asset's current **file** | becomes the new file's address (the old one moves to `versions[]`) |
354
+
355
+ ### Which to use
356
+
357
+ | You're showing it in… | Use |
358
+ |---|---|
359
+ | Static content: a built site's HTML or CSS, an email, a banner, anything written once and published | `assetUrl` |
360
+ | A product or collection image (`heroImage`, `additionalImages`, `logoImage`, `headerImage`) | the record's `AssetRef`, read from the record (see [AssetRef](#assetref--slim-embedded-shape)) |
361
+ | Version history, a "compare versions" view, an exact file for an audit | `url` and `versions[]` |
362
+
363
+ ```html
364
+ <!-- In a built page: swap "Hero image" in the library and this shows the new one, no rebuild -->
365
+ <img src="https://cdn.smartlinks.app/sites/my-collection/a/3f0c…" alt="…">
366
+ ```
367
+
368
+ ```typescript
369
+ const hero = await Api.asset.getAdmin('my-collection', heroId)
370
+ const src = hero.assetUrl ?? hero.url // fall back for outside links and older assets
371
+ ```
372
+
373
+ ### How it behaves
374
+
375
+ - **Replace** (`replaceFile`, or a resumable upload with `replaceAssetId`): `assetUrl` shows the new file. The CDN
376
+ copy is cleared at once, and browsers re-check within a minute. There's no redeploy and no extra API call.
377
+ - **Delete**: `assetUrl` stops serving (the asset is withdrawn). **Restore** brings it back.
378
+ - `assetUrl` is `null` for an outside link (an asset that is a link to someone else's file). Fall back to `url`.
379
+ - Assets made before asset addresses existed have `assetUrl: null` until they're given one:
380
+
381
+ ```typescript
382
+ const a = await Api.asset.ensureAssetUrl('my-collection', 'abc123')
383
+ a.assetUrl // https://cdn.smartlinks.app/sites/my-collection/a/abc123
384
+ ```
324
385
 
325
386
  ---
326
387