@proveanything/smartlinks 2.0.70 → 2.0.71

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.0.71 | Generated: 2026-10-24T17:23:20.711Z
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
@@ -3817,6 +3818,7 @@ interface Asset {
3817
3818
  proofId: string | null
3818
3819
  appId: string | null
3819
3820
  url: string
3821
+ assetUrl: string | null
3820
3822
  thumbnail: string | null
3821
3823
  name: string
3822
3824
  cleanName: string | null
@@ -4054,6 +4056,7 @@ interface CreateResumableUploadOptions {
4054
4056
  appId?: string
4055
4057
  admin?: boolean
4056
4058
  token?: string
4059
+ replaceAssetId?: string
4057
4060
  }
4058
4061
  ```
4059
4062
 
@@ -5065,6 +5068,329 @@ interface BatchTag {
5065
5068
  }
5066
5069
  ```
5067
5070
 
5071
+ ### bookings
5072
+
5073
+ **BookingAllocation** (interface)
5074
+ ```typescript
5075
+ interface BookingAllocation {
5076
+ id: string
5077
+ bookingId: string | null
5078
+ slotId: string | null
5079
+ resourceType: string
5080
+ resourceId: string
5081
+ startsAt: string
5082
+ endsAt: string
5083
+ quantity: number
5084
+ mode: 'exclusive' | 'capacity'
5085
+ active: boolean
5086
+ sourceSystem?: string | null
5087
+ data: Record<string, any>
5088
+ }
5089
+ ```
5090
+
5091
+ **Booking** (interface)
5092
+ ```typescript
5093
+ interface Booking {
5094
+ id: string
5095
+ collectionId: string
5096
+ appId: string
5097
+ visibility: BookingVisibility
5098
+ contactId?: string
5099
+ status: BookingStatus
5100
+ startsAt: string
5101
+ endsAt: string
5102
+ timezone?: string | null
5103
+ quantity: number
5104
+ holdExpiresAt?: string | null
5105
+ flags: string[]
5106
+ cancelReason?: string | null
5107
+ idempotencyKey?: string | null
5108
+ seriesId?: string | null
5109
+ ref?: string | null
5110
+ customId?: string | null
5111
+ sourceSystem?: string | null
5112
+ productId?: string | null
5113
+ proofId?: string
5114
+ parentType?: string | null
5115
+ parentId?: string | null
5116
+ createdAt: string
5117
+ updatedAt: string
5118
+ cancelledAt?: string | null
5119
+ data: Record<string, any>
5120
+ owner?: Record<string, any>
5121
+ admin?: Record<string, any>
5122
+ allocations: BookingAllocation[]
5123
+ editToken?: string
5124
+ }
5125
+ ```
5126
+
5127
+ **BookingSlot** (interface)
5128
+ ```typescript
5129
+ interface BookingSlot {
5130
+ id: string
5131
+ collectionId: string
5132
+ appId: string
5133
+ visibility: BookingVisibility
5134
+ resourceType?: string | null
5135
+ resourceId?: string | null
5136
+ startsAt: string
5137
+ endsAt: string
5138
+ timezone?: string | null
5139
+ capacity: number
5140
+ booked: number
5141
+ remaining: number
5142
+ perContactLimit?: number | null
5143
+ status: 'open' | 'closed' | 'cancelled'
5144
+ seriesId?: string | null
5145
+ ref?: string | null
5146
+ customId?: string | null
5147
+ sourceSystem?: string | null
5148
+ productId?: string | null
5149
+ createdAt: string
5150
+ updatedAt: string
5151
+ data: Record<string, any>
5152
+ owner?: Record<string, any>
5153
+ admin?: Record<string, any>
5154
+ }
5155
+ ```
5156
+
5157
+ **CreateBookingInput** (interface)
5158
+ ```typescript
5159
+ interface CreateBookingInput {
5160
+ slotId?: string
5161
+ quantity?: number
5162
+ resourceType?: string
5163
+ resourceId?: string
5164
+ resource?: { type: string; id: string }
5165
+ startsAt?: string | Date
5166
+ endsAt?: string | Date
5167
+ allocations?: BookingClaimInput[]
5168
+ status?: 'held' | 'pending' | 'confirmed' | 'waitlisted'
5169
+ holdMinutes?: number
5170
+ bufferBefore?: number
5171
+ bufferAfter?: number
5172
+ idempotencyKey?: string
5173
+ contactId?: string
5174
+ timezone?: string
5175
+ visibility?: BookingVisibility
5176
+ ref?: string
5177
+ customId?: string
5178
+ sourceSystem?: string
5179
+ productId?: string
5180
+ proofId?: string
5181
+ parentType?: string
5182
+ parentId?: string
5183
+ seriesId?: string
5184
+ data?: Record<string, any>
5185
+ owner?: Record<string, any>
5186
+ admin?: Record<string, any>
5187
+ }
5188
+ ```
5189
+
5190
+ **UpdateBookingInput** (interface)
5191
+ ```typescript
5192
+ interface UpdateBookingInput {
5193
+ data?: Record<string, any>
5194
+ owner?: Record<string, any>
5195
+ admin?: Record<string, any>
5196
+ visibility?: BookingVisibility
5197
+ ref?: string
5198
+ timezone?: string
5199
+ productId?: string
5200
+ proofId?: string
5201
+ contactId?: string
5202
+ sourceSystem?: string
5203
+ customId?: string
5204
+ flags?: string[]
5205
+ }
5206
+ ```
5207
+
5208
+ **MoveBookingInput** (interface)
5209
+ ```typescript
5210
+ interface MoveBookingInput {
5211
+ slotId?: string
5212
+ resourceType?: string
5213
+ resourceId?: string
5214
+ resource?: { type: string; id: string }
5215
+ startsAt?: string | Date
5216
+ endsAt?: string | Date
5217
+ allocations?: BookingClaimInput[]
5218
+ bufferBefore?: number
5219
+ bufferAfter?: number
5220
+ }
5221
+ ```
5222
+
5223
+ **BookingListQueryParams** (interface)
5224
+ ```typescript
5225
+ interface BookingListQueryParams {
5226
+ status?: string
5227
+ contactId?: string
5228
+ slotId?: string
5229
+ resourceType?: string
5230
+ resourceId?: string
5231
+ from?: string | Date
5232
+ to?: string | Date
5233
+ ref?: string
5234
+ customId?: string
5235
+ sourceSystem?: string
5236
+ productId?: string
5237
+ seriesId?: string
5238
+ sort?: string
5239
+ limit?: number
5240
+ offset?: number
5241
+ }
5242
+ ```
5243
+
5244
+ **CreateSlotInput** (interface)
5245
+ ```typescript
5246
+ interface CreateSlotInput {
5247
+ startsAt: string | Date
5248
+ endsAt: string | Date
5249
+ capacity: number
5250
+ perContactLimit?: number
5251
+ resourceType?: string
5252
+ resourceId?: string
5253
+ blockResource?: boolean
5254
+ status?: 'open' | 'closed' | 'cancelled'
5255
+ timezone?: string
5256
+ visibility?: BookingVisibility
5257
+ seriesId?: string
5258
+ ref?: string
5259
+ customId?: string
5260
+ sourceSystem?: string
5261
+ productId?: string
5262
+ data?: Record<string, any>
5263
+ owner?: Record<string, any>
5264
+ admin?: Record<string, any>
5265
+ }
5266
+ ```
5267
+
5268
+ **SlotListQueryParams** (interface)
5269
+ ```typescript
5270
+ interface SlotListQueryParams {
5271
+ status?: string
5272
+ available?: boolean
5273
+ from?: string | Date
5274
+ to?: string | Date
5275
+ resourceType?: string
5276
+ resourceId?: string
5277
+ productId?: string
5278
+ seriesId?: string
5279
+ ref?: string
5280
+ customId?: string
5281
+ sort?: string
5282
+ limit?: number
5283
+ offset?: number
5284
+ }
5285
+ ```
5286
+
5287
+ **CreateBlockInput** (interface)
5288
+ ```typescript
5289
+ interface CreateBlockInput {
5290
+ resourceType: string
5291
+ resourceId: string
5292
+ startsAt: string | Date
5293
+ endsAt: string | Date
5294
+ sourceSystem?: string
5295
+ data?: Record<string, any>
5296
+ }
5297
+ ```
5298
+
5299
+ **BlockListQueryParams** (interface)
5300
+ ```typescript
5301
+ interface BlockListQueryParams {
5302
+ resourceType?: string
5303
+ resourceId?: string
5304
+ from?: string | Date
5305
+ to?: string | Date
5306
+ limit?: number
5307
+ offset?: number
5308
+ }
5309
+ ```
5310
+
5311
+ **FreeBusyQuery** (interface)
5312
+ ```typescript
5313
+ interface FreeBusyQuery {
5314
+ resources: BookingResourceRef[]
5315
+ from: string | Date
5316
+ to: string | Date
5317
+ }
5318
+ ```
5319
+
5320
+ **FreeBusyInterval** (interface)
5321
+ ```typescript
5322
+ interface FreeBusyInterval {
5323
+ start: string
5324
+ end: string
5325
+ }
5326
+ ```
5327
+
5328
+ **FreeBusyResponse** (interface)
5329
+ ```typescript
5330
+ interface FreeBusyResponse {
5331
+ from: string
5332
+ to: string
5333
+ resources: Array<{ resourceType: string; resourceId: string; busy: FreeBusyInterval[] }>
5334
+ }
5335
+ ```
5336
+
5337
+ **BookingAccessOptions** (interface)
5338
+ ```typescript
5339
+ interface BookingAccessOptions {
5340
+ editToken?: string
5341
+ }
5342
+ ```
5343
+
5344
+ **ScheduleHours** (interface)
5345
+ ```typescript
5346
+ interface ScheduleHours {
5347
+ start: string
5348
+ end: string
5349
+ }
5350
+ ```
5351
+
5352
+ **ScheduleOverride** (interface)
5353
+ ```typescript
5354
+ interface ScheduleOverride {
5355
+ date?: string
5356
+ from?: string
5357
+ to?: string
5358
+ hours: ScheduleHours[]
5359
+ }
5360
+ ```
5361
+
5362
+ **StandardSchedule** (interface)
5363
+ ```typescript
5364
+ interface StandardSchedule {
5365
+ timezone: string
5366
+ weekly?: Partial<Record<ScheduleDay, ScheduleHours[]>>
5367
+ overrides?: ScheduleOverride[]
5368
+ }
5369
+ ```
5370
+
5371
+ **BookingStatus** = ``
5372
+
5373
+ **BookingVisibility** = `'public' | 'owner' | 'admin'`
5374
+
5375
+ **BookingClaimInput** = ``
5376
+
5377
+ **UpdateSlotInput** = `Partial<Omit<CreateSlotInput, 'blockResource' | 'seriesId' | 'resourceType' | 'resourceId'>>`
5378
+
5379
+ **BookingResourceRef** (type)
5380
+ ```typescript
5381
+ type BookingResourceRef = { type: string; id: string }
5382
+ ```
5383
+
5384
+ **BookingList** = `PaginatedResponse<Booking>`
5385
+
5386
+ **SlotList** = `PaginatedResponse<BookingSlot>`
5387
+
5388
+ **BlockList** = `PaginatedResponse<BookingAllocation>`
5389
+
5390
+ **BookingErrorCode** = ``
5391
+
5392
+ **ScheduleDay** = `'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun'`
5393
+
5068
5394
  ### broadcasts
5069
5395
 
5070
5396
  **ListBroadcastsQuery** (interface)
@@ -10568,6 +10894,41 @@ interface ConditionDebugOptions {
10568
10894
 
10569
10895
  **ConditionDebugLogger** = `(...args: any[]) => void`
10570
10896
 
10897
+ ### freeTimes (utils)
10898
+
10899
+ **TimeInterval** (interface)
10900
+ ```typescript
10901
+ interface TimeInterval {
10902
+ start: number
10903
+ end: number
10904
+ }
10905
+ ```
10906
+
10907
+ **FreeTimesOptions** (interface)
10908
+ ```typescript
10909
+ interface FreeTimesOptions {
10910
+ hours: StandardSchedule | TimeInterval[]
10911
+ busy?: Array<FreeBusyInterval | TimeInterval>
10912
+ from: string | number | Date
10913
+ to: string | number | Date
10914
+ duration: number
10915
+ step?: number
10916
+ bufferBefore?: number
10917
+ bufferAfter?: number
10918
+ minNotice?: number
10919
+ now?: number
10920
+ buffersWithinHours?: boolean
10921
+ }
10922
+ ```
10923
+
10924
+ **FreeTimeSlot** (interface)
10925
+ ```typescript
10926
+ interface FreeTimeSlot {
10927
+ start: string
10928
+ end: string
10929
+ }
10930
+ ```
10931
+
10571
10932
  ### paths (utils)
10572
10933
 
10573
10934
  **PortalPathParams** (interface)
@@ -11065,6 +11426,9 @@ Soft-delete an asset. Schedules CDN purge after `graceDays` (default 30). Recove
11065
11426
  **restoreAdmin**(collectionId: string, assetId: string) → `Promise<Asset>`
11066
11427
  Restore a soft-deleted asset (clears `deletedAt`).
11067
11428
 
11429
+ **ensureAssetUrl**(collectionId: string, assetId: string) → `Promise<Asset>`
11430
+ 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`.
11431
+
11068
11432
  **bulkDelete**(options: BulkDeleteAssetsOptions) → `Promise<`
11069
11433
  Soft-delete multiple assets in one request.
11070
11434
 
@@ -11468,6 +11832,72 @@ Find a specific batch by ID across all products in a collection. Returns the bat
11468
11832
  claimSetId?: string) → `Promise<BatchTag[]>`
11469
11833
  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
11834
 
11835
+ ### bookings
11836
+
11837
+ **create**(collectionId: string, appId: string, input: CreateBookingInput, admin: boolean = false) → `Promise<Booking>`
11838
+ 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
11839
+
11840
+ **list**(collectionId: string, appId: string, params?: BookingListQueryParams, admin: boolean = true) → `Promise<PaginatedResponse<Booking>>`
11841
+ List bookings (admin). GET /bookings
11842
+
11843
+ **mine**(collectionId: string, appId: string, params?: BookingListQueryParams) → `Promise<PaginatedResponse<Booking>>`
11844
+ The signed-in caller's own bookings (public). GET /bookings/mine
11845
+
11846
+ **get**(collectionId: string, appId: string, bookingId: string, admin: boolean = false, opts?: BookingAccessOptions) → `Promise<Booking>`
11847
+ Get one booking. Public: the signed-in booker, or a guest passing `{ editToken }`. GET /bookings/:bookingId
11848
+
11849
+ **update**(collectionId: string, appId: string, bookingId: string, input: UpdateBookingInput, admin: boolean = true) → `Promise<Booking>`
11850
+ Update zones and descriptive fields (admin). PATCH /bookings/:bookingId
11851
+
11852
+ **confirm**(collectionId: string, appId: string, bookingId: string, admin: boolean = false, opts?: BookingAccessOptions) → `Promise<Booking>`
11853
+ 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
11854
+
11855
+ **cancel**(collectionId: string, appId: string, bookingId: string, admin: boolean = false, opts?: BookingAccessOptions & { reason?: string }) → `Promise<Booking>`
11856
+ 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
11857
+
11858
+ **move**(collectionId: string, appId: string, bookingId: string, input: MoveBookingInput, admin: boolean = false, opts?: BookingAccessOptions) → `Promise<Booking>`
11859
+ 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
11860
+
11861
+ **extendHold**(collectionId: string, appId: string, bookingId: string, holdMinutes: number) → `Promise<Booking>`
11862
+ Extend a hold by `holdMinutes` from now (admin; capped at 24h after the booking was made).
11863
+
11864
+ **complete**(collectionId: string, appId: string, bookingId: string) → `Promise<Booking>`
11865
+ confirmed → completed (admin).
11866
+
11867
+ **noShow**(collectionId: string, appId: string, bookingId: string) → `Promise<Booking>`
11868
+ confirmed → no_show (admin).
11869
+
11870
+ **freebusy**(collectionId: string, appId: string, q: FreeBusyQuery, admin: boolean = false) → `Promise<FreeBusyResponse>`
11871
+ 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
11872
+
11873
+ ### bookings.blocks
11874
+
11875
+ **create**(collectionId: string, appId: string, input: CreateBlockInput) → `Promise<BookingAllocation>`
11876
+
11877
+ **list**(collectionId: string, appId: string, params?: BlockListQueryParams) → `Promise<PaginatedResponse<BookingAllocation>>`
11878
+
11879
+ **remove**(collectionId: string, appId: string, blockId: string) → `Promise<`
11880
+
11881
+ ### bookings.slots
11882
+
11883
+ **create**(collectionId: string, appId: string, input: CreateSlotInput) → `Promise<BookingSlot>`
11884
+ Create a slot (admin). POST /bookings/slots
11885
+
11886
+ **createMany**(collectionId: string, appId: string, slots: CreateSlotInput[], opts?: { seriesId?: string }) → `Promise<`
11887
+ Create many slots at once — a series — all-or-nothing (admin, max 500). POST /bookings/slots/batch
11888
+
11889
+ **list**(collectionId: string, appId: string, params?: SlotListQueryParams, admin: boolean = false) → `Promise<PaginatedResponse<BookingSlot>>`
11890
+ List slots. Public: open, public slots only, each with `remaining`. GET /bookings/slots
11891
+
11892
+ **get**(collectionId: string, appId: string, slotId: string, admin: boolean = false) → `Promise<BookingSlot>`
11893
+ GET /bookings/slots/:slotId
11894
+
11895
+ **update**(collectionId: string, appId: string, slotId: string, input: UpdateSlotInput) → `Promise<BookingSlot>`
11896
+ 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).
11897
+
11898
+ **remove**(collectionId: string, appId: string, slotId: string) → `Promise<`
11899
+ Delete a slot (admin; refused while places are booked).
11900
+
11471
11901
  ### broadcasts
11472
11902
 
11473
11903
  **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
package/docs/assets.md CHANGED
@@ -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