@gradial/aci 0.1.52-rc.29 → 0.1.52-rc.34-e2e.1

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +4 -4
  3. package/dist/audience-data-models.d.ts +201 -372
  4. package/dist/audience-data-models.js +283 -120
  5. package/dist/cli/compile-registry.mjs +0 -2
  6. package/dist/collect/aci.js +1 -1
  7. package/dist/collect/aci.js.sha256 +1 -1
  8. package/dist/collect/gapbuffer.js +11 -6
  9. package/dist/internal/platform/collectionstore/errors.d.ts +11 -0
  10. package/dist/internal/platform/collectionstore/errors.js +20 -0
  11. package/dist/next/error-pages.d.ts +4 -0
  12. package/dist/next/error-pages.js +4 -0
  13. package/dist/next/release-context.d.ts +29 -0
  14. package/dist/next/release-context.js +55 -0
  15. package/dist/next/server.d.ts +8 -0
  16. package/dist/next/server.js +14 -1
  17. package/dist/react/VideoPlayer.d.ts +1 -1
  18. package/dist/react/VideoPlayer.js +2 -2
  19. package/dist/react/index.d.ts +2 -2
  20. package/dist/react/index.js +2 -3
  21. package/dist/testing/conformance/preset.js +3 -0
  22. package/dist/testing/conformance/root-identity.js +7 -26
  23. package/dist/testing/conformance/types.d.ts +3 -0
  24. package/dist/types/image.d.ts +54 -17
  25. package/dist/types/image.js +102 -18
  26. package/dist/types/media.d.ts +14 -6
  27. package/dist/types/video.d.ts +7 -3
  28. package/migrations/conformance-registry.md +22 -0
  29. package/package.json +8 -7
  30. package/src/cli/compile-registry.mjs +0 -2
  31. package/src/types/image.ts +133 -29
  32. package/src/types/video.ts +1 -1
  33. package/templates/astro/starter-content/src/fixtures/sections.ts +0 -1
  34. package/templates/common/.agents/skills/aci/SKILL.md +4 -3
  35. package/templates/common/.agents/skills/dam-delivery/SKILL.md +182 -193
  36. package/templates/common/.agents/skills/dam-delivery/references/permalink-grammar.md +137 -101
  37. package/templates/common/starter-content/.content/dam/README.md +74 -0
  38. package/templates/common/starter-content/.content/dam/folders/seed/000000000000000000000000101/governance.json +2 -1
  39. package/templates/common/starter-content/.content/dam/folders/seed/000000000000000000000000102/governance.json +2 -1
  40. package/templates/common/starter-content/.content/patterns/components/button.json +2 -2
  41. package/templates/common/starter-content/.content/patterns/components/container.json +2 -2
  42. package/templates/common/starter-content/.content/patterns/components/feature_card.json +2 -2
  43. package/templates/common/starter-content/.content/patterns/components/feature_grid.json +2 -2
  44. package/templates/common/starter-content/.content/patterns/components/home_hero.json +16 -7
  45. package/templates/common/starter-content/.content/patterns/components/personalization_choice.json +2 -2
  46. package/templates/common/starter-content/.content/patterns/components/site-footer.json +2 -2
  47. package/templates/common/starter-content/.content/patterns/components/site-navigation.json +2 -2
  48. package/templates/common/starter-content/.content/patterns/components/stats_bar.json +2 -2
  49. package/templates/nextjs/template/src/app/fonts/inter/OFL.txt +93 -0
  50. package/templates/nextjs/template/src/app/fonts/inter/inter-latin.woff2 +0 -0
  51. package/templates/nextjs/template/src/app/fonts/space-grotesk/OFL.txt +93 -0
  52. package/templates/nextjs/template/src/app/fonts/space-grotesk/space-grotesk-latin.woff2 +0 -0
  53. package/templates/nextjs/template/src/app/layout.tsx +10 -7
  54. package/templates/nextjs/template/tests/conformance.test.ts.tmpl +1 -0
  55. package/templates/common/.agents/skills/dam-delivery/references/parameter-contract.md +0 -107
@@ -1,228 +1,217 @@
1
1
  ---
2
2
  name: dam-delivery
3
- description: Mint and use signed DAM delivery URLs for on-demand image transforms, and manage public permanent URLs (permalinks) for CMS fields and email. Use when a page or server function needs a resized, cropped, or format-converted image at render time, when choosing between pre-baked derivatives and dynamic transforms, when an image URL must be pasted somewhere that cannot call a mint API (Contentful-class CMS fields, Marketo/SFMC email), or when a delivery URL returns 403/404 unexpectedly.
3
+ description: Build and use public DAM asset links (`/dam/{assetId}`) with on-the-fly image shaping (resize, crop, format, quality). Use when a page, a CMS field, or an email needs a URL for an approved DAM asset, when choosing between a compiled content link and a shaped public link, when making an asset public or taking it down, or when a `/dam/…` URL answers 301, 403, or 404 unexpectedly.
4
4
  ---
5
5
 
6
- # DAM Delivery — Signed Transform URLs
6
+ # DAM Delivery — Public Asset Links
7
7
 
8
- The DAM serves parameterized image transforms (resize, crop, quality,
9
- webp/avif) through signed URLs under `/dam/i/{assetId}/…`. The API mints
10
- them; clients can neither construct nor alter them — every URL is
11
- HMAC-signed server-side over a closed, validated parameter contract.
12
-
13
- ## The One Rule: URLs Are Render-Time Artifacts
14
-
15
- Delivery URLs **expire** (24h maximum by default) and are **banned from
16
- published payloads**. The publish validator rejects any payload containing
17
- the `/dam/i/` path marker, exactly as it rejects raw `dam/blobs` pointers.
18
-
19
- - **DO** mint at render time: SSR handlers, server functions, API-backed
20
- routes, previews.
21
- - **DO NOT** bake minted URLs into content JSON, compiled payloads, or
22
- anything that gets published — a frozen release would carry URLs that
23
- permanently 404 within a day.
24
- - For content baked into releases, keep using the pre-baked derivative
25
- pipeline (`imageSlots` in contracts → `dam/derivatives/…` URLs via
26
- `imageAttrs()`/`Picture`). Delivery URLs complement it; they do not
27
- replace it.
28
-
29
- ## Minting
30
-
31
- One endpoint mints single or batch; the request is always an array of
32
- parameter sets and one signed URL comes back per set, in order.
8
+ Every approved DAM asset has one public address on the site's own domain:
33
9
 
34
10
  ```
35
- POST /v1/dam/assets/{assetId}/delivery-urls
36
- {
37
- "requests": [
38
- { "width": 800, "height": 600, "fit": "fill", "gravity": "noea",
39
- "quality": 90, "dpr": 2, "format": "avif" },
40
- { "width": 400 }
41
- ]
42
- }
11
+ https://{site}/dam/{assetId}
43
12
  ```
44
13
 
45
- Response — exactly one of `url`/`error` per item:
14
+ Add a query to shape an image on the fly:
46
15
 
47
- ```json
48
- {
49
- "results": [
50
- { "url": "/dam/i/ast_…/…", "expiresAt": "2026-08-16T12:00:00Z" },
51
- { "error": { "status": 400, "code": "dam.delivery.invalid_param",
52
- "message": "width must be 1-4096", "details": { "param": "width" } } }
53
- ]
54
- }
55
16
  ```
56
-
57
- Asset-level failures (asset not found, unavailable, permission denied)
58
- fail the whole request; parameter failures are per-item so one bad set
59
- cannot sink its siblings. Batches cap at 100 parameter sets.
60
-
61
- CLI equivalents:
62
-
63
- ```bash
64
- # Single mint from flags
65
- aci dam delivery-url ast_2E9qL0001aBcDeF --width 800 --height 600 \
66
- --fit fill --gravity noea --quality 90 --format avif --dpr 2
67
-
68
- # Batch from a JSON array (prints one URL<TAB>expiresAt line per mint)
69
- aci dam delivery-url ast_2E9qL0001aBcDeF \
70
- --params '[{"width":400},{"width":800,"dpr":2}]'
17
+ https://{site}/dam/{assetId}?w=640&h=384&fit=crop&fm=jpg&q=80
71
18
  ```
72
19
 
73
- ## The Parameter Contract (Closed)
20
+ The link is built from the asset ID alone. Nothing is minted or signed and
21
+ nothing expires, so the same link can sit in a page, a CMS field, or an
22
+ email for years. It always serves the newest approved public version of
23
+ the asset.
74
24
 
75
- Everything outside this table is rejected. Full ranges, interactions, and
76
- error codes: `references/parameter-contract.md`.
25
+ ## Retired — Do Not Use
77
26
 
78
- | Param | Values | Default | Notes |
79
- | --- | --- | --- | --- |
80
- | `width`, `height` | 1–4096 | none | `width·dpr` and `height·dpr` must each stay ≤ 4096 |
81
- | `fit` | `fit` \| `fill` | `fit` | requires `width` or `height` |
82
- | `gravity` | `ce`, `no`, `so`, `ea`, `we`, `noea`, `nowe`, `soea`, `sowe`, or `fp:{x}:{y}` (x,y ∈ [0,1]) | `ce` | non-default gravity requires `fit: "fill"` |
83
- | `quality` | 1–100 | 82 | |
84
- | `dpr` | 1–3 | 1 | multiplies output dimensions |
85
- | `format` | `webp` \| `avif` \| `jpeg` \| `png` | `webp` | becomes the URL extension |
86
-
87
- Special cases:
88
-
89
- - **SVG** is passthrough-only: only the empty parameter set `{}` is
90
- accepted; any transform param is rejected.
91
- - **Non-image assets** are never served through `/dam/i/*`; use the
92
- download endpoints instead.
93
- - Oversized blobs (over the origin's 200 MB streaming envelope) refuse to
94
- mint with `dam.delivery.source_too_large`.
95
-
96
- ## Expiry Semantics
97
-
98
- - Every URL carries a signed expiry; `expiresAt` in the response is that
99
- instant. After it, the URL 404s at the origin and cached copies die at
100
- the CDN.
101
- - The bound is quantized (default 1h): identical requests minted within
102
- the same quantum return **byte-equal URLs**, so re-minting per render is
103
- cheap and cache-friendly. Effective lifetime is between `maxTTL − 1h`
104
- and `maxTTL` (default 24h).
105
- - A version whose scheduled expiry revokes delivery gets URLs that expire
106
- exactly at that boundary, which may be much sooner. Never assume a
107
- fixed TTL — read `expiresAt`.
108
- - Re-mint instead of caching URLs long-term. Caching a minted URL for the
109
- duration of one render/request is fine.
110
-
111
- ## Availability: Why Mints Get Denied
112
-
113
- Minting serves **only the latest approved, publishable version** and fails
114
- closed. Expect typed errors (HTTP 422 unless noted):
115
-
116
- | Code | Meaning |
117
- | --- | --- |
118
- | `dam.asset.no_approved_version` | draft-only, embargoed, expired, or usage-restricted versions — nothing publishable |
119
- | `dam.asset.not_active` | archived or deleted |
120
- | `dam.asset.legal_hold` | under legal hold |
121
- | `dam.asset.blocked` | asset-wide publication block (usage restriction) |
122
- | `dam.permission_denied` | caller lacks download permission (403) |
123
- | `dam.delivery.not_image` | non-image MIME |
124
-
125
- There is no fallback to unapproved versions anywhere. Approval changes URL
126
- identity (new blob → new URLs on next mint); already-minted URLs keep
127
- serving what they named until they expire.
27
+ Earlier SDK releases documented two delivery lanes that no longer exist.
28
+ If you meet any of these in old content, notes, or code, replace them with
29
+ the link above. They are not served and they do not redirect.
128
30
 
129
- ## Public Permanent URLs (Permalinks)
31
+ | Retired | Now |
32
+ | --- | --- |
33
+ | Signed URLs under `/dam/i/…`, `POST /v1/dam/assets/{assetId}/delivery-urls`, `aci dam delivery-url` | Build `/dam/{assetId}?…` yourself; there is nothing to mint |
34
+ | Permalinks under `/dam/a/…`, `GET /v1/dam/assets/{assetId}/permalinks`, recipe URLs | `/dam/{assetId}?…` |
35
+ | The `lifecycle.publicDelivery` flag | Approving a version makes it public (see below) |
36
+ | `width`, `height`, `gravity`, `dpr`, `quality`, `format` | `w`, `h`, `fit`, `fm`, `q` |
130
37
 
131
- Assets flagged `lifecycle.publicDelivery` also serve through a **permanent,
132
- public URL** — Sanity-style, deterministic from the assetId alone, no mint
133
- API in the loop:
38
+ ## The Link
134
39
 
135
40
  ```
136
- https://{site}/dam/a/{assetId}?w=640&h=384&fit=crop&fm=jpg&q=80
41
+ /dam/{assetId}[/{slug}][?w=…&h=…&fit=…&fm=…&q=…]
137
42
  ```
138
43
 
139
- It always resolves to the **current approved version** (approve a new
140
- version and pasted URLs pick it up within ~30s, no re-paste), and it dies
141
- within the revocation budget (≤ 30s for fresh resolution) on embargo,
142
- expiry, archive, or unpublish. Transform parameters are
143
- client-constructable but **validated and quantized** at the edge — `w`/`h`
144
- snap down to a fixed ladder, `q` to steps of 5 — and unknown parameter
145
- names (`utm_*`, `mkt_tok`, any ESP decoration) are **stripped, never
146
- errors**, so tracking-decorated links keep working. Invalid *values* of
147
- the contract params (`fm=bmp`, `w=abc`) are a 400: author error fails
148
- loudly. Full grammar, quantization ladder, and Sanity-style examples:
44
+ - **`assetId`** is a raw 27-character ID made of digits and letters
45
+ (`0-9A-Za-z`), with no prefix. IDs that start with `ast_` are retired
46
+ and rejected; per ADR 0043 they are replaced by the breaking migration,
47
+ with no compatibility period.
48
+ - **`slug`** is optional and cosmetic: `/dam/{assetId}/spring-sale.jpg`
49
+ answers with a permanent redirect to `/dam/{assetId}`, keeping the query.
50
+ Use it when a tool insists on a URL that ends in a file extension. The
51
+ slug is never checked against anything, and its extension does not pick
52
+ the format.
53
+ - The CLI prints the path for an ID. It is a formatter only: it makes no
54
+ API call and does not tell you whether the asset is public.
55
+
56
+ ```bash
57
+ aci dam permalink 2E9qL0001aBcDeFgHiJkLmNoPqR
58
+ # /dam/2E9qL0001aBcDeFgHiJkLmNoPqR
59
+ ```
60
+
61
+ The route is served by the deployed site's hosting edge. The site's own
62
+ dev server (`npm run dev`) serves `/dam/…` links for assets seeded in the
63
+ local `.content/dam/` tree; anything else answers 404 there — test it
64
+ against the deployed domain.
65
+
66
+ ## Shaping an Image
67
+
68
+ Five query parameters, and only these. Full rules and worked examples:
149
69
  `references/permalink-grammar.md`.
150
70
 
151
- **When to use which lane:**
152
-
153
- - **Signed delivery URLs** (`/dam/i/*`, above): everything that renders
154
- server-side and can re-mint — SSR, server functions, previews.
155
- - **Permalinks** (`/dam/a/*`): places where a URL is pasted once and must
156
- live for years with no code in the loop — CMS fields, Marketo/SFMC
157
- email. Requires the asset to be flagged public.
158
-
159
- ### Getting the URL
71
+ | Param | Values | Default | Notes |
72
+ | --- | --- | --- | --- |
73
+ | `w`, `h` | whole numbers | none | 1–4096, honored exactly — no snapping or size ladder |
74
+ | `fit` | `max` \| `crop` | `max` | `max` fits inside the box; `crop` fills it and needs both `w` and `h` |
75
+ | `fm` | `webp` \| `avif` \| `jpg` \| `png` | `webp` | the format is always this parameter, never the browser's `Accept` header |
76
+ | `q` | 5–100 | 80 | snapped down to steps of 5 |
77
+
78
+ Three things to know before writing a link:
79
+
80
+ - **Any one parameter turns shaping on, and the rest take their
81
+ defaults.** `?w=640` serves WebP at quality 80, not the original format.
82
+ To keep a JPEG a JPEG, say `fm=jpg`.
83
+ - **No parameters means the original.** `/dam/{assetId}` streams the
84
+ approved file byte for byte.
85
+ - **Nothing is rejected.** Unknown parameter names (`utm_*`, `mkt_tok`,
86
+ cache busters) are dropped. Out-of-range or unrecognised values are
87
+ snapped to an allowed one, sizes and quality always downward: `w=333`
88
+ serves 256, `q=77` serves 75, `fm=gif` serves WebP. Do not expect
89
+ pixel-exact sizes off the ladder. A `w`, `h`, or `q` that is not a
90
+ whole number (`w=abc`, `q=high`) is treated as if it had been left out,
91
+ so a typo never shrinks an image or lowers its quality.
92
+
93
+ The edge settles every link on one canonical form: the parameters in the
94
+ order `w`, `h`, `fit`, `fm`, `q`, after snapping, with `fit`, `fm`, and
95
+ `q` always present. A link written any other way still works. A request
96
+ already on `/dam/{assetId}` with a noncanonical query is answered with one
97
+ permanent redirect to the canonical link (sites hosted on CloudFront tidy
98
+ the query without a visible redirect). A request carrying a slug and a
99
+ noncanonical query can redirect twice: once to drop the slug, once to
100
+ canonicalize the query. Write the canonical form when you control the URL
101
+ and you skip those hops.
102
+
103
+ Shaping only applies to JPEG, PNG, WebP, and AVIF originals. Everything
104
+ else, SVG included, is served only as a file download, never displayed
105
+ inline. On AWS CloudFront this holds only once the hosting fix (PR #709)
106
+ has rolled out to the tenant; until then that lane may serve SVG or HTML
107
+ inline. See the reference for the size limits.
108
+
109
+ ## What Makes a Link Serve
110
+
111
+ Approving a version is the "make public" act. There is no separate flag.
160
112
 
161
113
  ```
162
- GET /v1/dam/assets/{assetId}/permalinks
163
- ```
164
-
165
- Returns the base permalink and per-recipe URLs (path + absolute URL), the
166
- grammar constants (width ladder, quality step, defaults), the
167
- `publicDelivery` flag state, and a `warning` field (below). CLI:
168
-
169
- ```bash
170
- aci dam permalink ast_2E9qL0001aBcDeF # base URL
171
- aci dam permalink ast_2E9qL0001aBcDeF --recipe email-hero
114
+ POST /v1/dam/assets/{assetId}/lifecycle
115
+ { "action": "approve", "versionId": "…", "expectedHead": "…" }
172
116
  ```
173
117
 
174
- The permalink path is deterministic, so the endpoint answers even before
175
- the flag is flipped — check `publicDelivery` to know whether it resolves.
118
+ Read `effects` in the response to see what happened:
176
119
 
177
- ### Flipping the Flag
178
-
179
- `publicDelivery` is patched through the governance plane:
180
-
181
- ```
182
- PATCH /v1/dam/assets/{assetId}/governance
183
- { "expectedHead": "…", "lifecycle": { "publicDelivery": true } }
184
- ```
185
-
186
- Flipping **on** is a "make public" act and passes the eligibility gate
187
- against the version the permalink would serve. A refusal is a typed 400,
188
- code `dam.delivery.public_flip_denied`, with `details.reason` carrying the
189
- gate's own reason — e.g. `no_approved_version`, `not_image`, a
190
- `block_new_usage` restriction, a publication-less license, or an expiry
191
- the lane cannot enforce. Fix the governance fact; do not retry blind.
192
- Flipping **off** is a plain patch and kills public serving within the
193
- revocation budget.
194
-
195
- ### Doctrine for Email and CMS (the two rules)
196
-
197
- 1. **Paste the permalink; never let the ESP import the bytes.** Many email
198
- tools offer to "import" or "copy" images to their own CDN — refuse. An
199
- imported copy escapes governance and revocation forever; the permalink
200
- is the governed pointer, and it is precisely the ≤ 30s revocation
201
- budget that makes it safe to paste.
202
- 2. **Assets needing `block_new_usage` semantics do not get flagged
203
- public.** The permalink lane cannot tell an old placement from a new
204
- one, so `block_new_usage` cannot be enforced there — the flip gate
205
- refuses such assets at authoring time. Use `unpublish` or
206
- `block_all_usage` expiry actions (the lane enforces those boundaries
207
- exactly), or keep the asset on the signed lane. If `block_new_usage`
208
- governance lands on an *already-public* asset, the permalink keeps
209
- serving and the permalinks API surfaces
210
- `warning: "block_new_usage_in_force"` — treat that warning as "review
211
- this asset's public status now".
212
-
213
- Email format tip: Outlook desktop cannot render WebP — use `fm=jpg` or
214
- `fm=png` in email permalinks.
120
+ | Effect | Meaning |
121
+ | --- | --- |
122
+ | `live_published` | the link now serves this version |
123
+ | `public_admission_vetoed:<cause>` | the version is approved but **not public**: it carries an expiry date, an embargo still in force, a usage block, or a license that does not allow publication. The link keeps serving the previous public version, if there is one |
124
+ | `live_plane_unconfigured` | a local or dev stack with no delivery wired; nothing was made public |
125
+
126
+ On the asset record (`GET /v1/dam/assets/{assetId}`), a public version
127
+ shows `deliveryTier: "public"` in its governance, and a refused one shows
128
+ `tierAdmissionVeto`.
129
+
130
+ - **A newer approved version takes over the same link.** Nothing needs
131
+ re-pasting.
132
+ - **To take a link down**, `demote` the version (the link falls back to
133
+ the previous public version, or stops serving if there is none) or
134
+ `archive` the asset. Both use the same lifecycle endpoint.
135
+ - **Governance that would have blocked the approval cannot be added while
136
+ a version is public.** A patch adding an expiry date, an embargo, a
137
+ usage block, or a license without publication is refused with a
138
+ conflict that tells you to demote first.
139
+ - **Changes take up to five minutes to show everywhere.** Responses are
140
+ `Cache-Control: public, max-age=300`, so a new version or a take-down
141
+ can lag by that long in browsers and CDN caches. On AWS CloudFront,
142
+ cached copies can currently outlive that window — the image pipeline
143
+ sets a one-year TTL and the distribution permits it — until the hosting
144
+ lane is corrected; the five minutes holds for Vercel and Cloudflare. Do
145
+ not treat a public link as instantly revocable, and do not re-cache it
146
+ for longer downstream.
147
+
148
+ ## Compiled Content Link or a Public Link?
149
+
150
+ - **Images a page renders from content: use the compiled live link.**
151
+ Author a `dam.assetRef` (asset id + alt); the compiler emits the
152
+ versionless permalink and the SDK asset component builds the `srcset`
153
+ from the slot's declared widths at render time. Declare `imageSlots` in
154
+ the contract and render with `imageAttrs()` or `Picture`; you never
155
+ construct a URL. There is no pre-baked derivative pipeline — publish
156
+ stages no asset bytes.
157
+ - **A URL that lives outside the build: use the public link.** A field in
158
+ an external CMS, an email, a social card, a partner page, or a one-off
159
+ size that no slot declares.
160
+
161
+ The difference that matters: a compiled link follows approval and take-down
162
+ through each publish, while a public link follows them on its own, within
163
+ the five-minute window.
164
+
165
+ Published content may carry `/dam/{assetId}` links. The publish check
166
+ rejects a payload that contains a raw source path (`dam/blobs/…`) or a
167
+ retired `/dam/a/` or `/dam/i/` path.
168
+
169
+ ## Email and External CMS Fields
170
+
171
+ 1. **Paste the link; never let the tool import the bytes.** Many email
172
+ tools offer to "import" or "copy" images to their own CDN. Refuse. An
173
+ imported copy escapes approval and take-down forever; the link is the
174
+ governed pointer.
175
+ 2. **Always set `fm=jpg` or `fm=png` in email.** The default is WebP, and
176
+ Outlook desktop cannot render WebP.
177
+ 3. **Use the slug form when a tool wants a file extension:**
178
+ `/dam/{assetId}/spring-sale.jpg?w=640&fm=jpg`.
179
+ 4. **`block_new_usage` does not take a public link down.** The link cannot
180
+ tell an old placement from a new one, so an asset under that
181
+ restriction keeps serving where it is already pasted. To stop serving,
182
+ demote or archive.
183
+
184
+ ## When a Link Does Not Serve
185
+
186
+ Errors are never cached (`no-store`) on Vercel and Cloudflare. On AWS
187
+ CloudFront, 403 and 5xx responses may be cached by the distribution for up
188
+ to about ten seconds regardless of `no-store`. Exact codes differ slightly
189
+ between hosting providers, so treat any 4xx as "not served".
190
+
191
+ | You see | Why | Do |
192
+ | --- | --- | --- |
193
+ | 301 | a slug was dropped or the query was put in canonical form | follow it; write the canonical link next time |
194
+ | 403 | the asset has no public version (never approved, approval vetoed, demoted, archived, or expired), or the ID is malformed | check `effects` from the approve call and `deliveryTier` on the asset record |
195
+ | 404 | a retired `/dam/a/…` or `/dam/i/…` path, DAM delivery is not enabled for this site, or you are on the local dev server | rewrite the link; ask the operator to enable DAM delivery |
196
+ | 413 | the original is too large to shape | drop the query to get the original, or upload a smaller master |
197
+ | 415 | shaping was asked of a non-image original | drop the query |
198
+ | 503 | delivery could not read its own state and refused rather than guess | retry; if it persists, it is an operator issue |
199
+ | A download instead of an image | the original is not JPEG, PNG, WebP, or AVIF | by design; render SVGs through the site's own image pipeline |
215
200
 
216
201
  ## Draft Previews (Management Surfaces Only)
217
202
 
218
- Drafts are reachable only by naming their version — authenticated, never
219
- CDN-cached, never via `/dam/i/*`:
203
+ The public link never serves a draft. A draft or rejected version is
204
+ reachable only by an authenticated caller with read permission on the
205
+ asset, and only by naming the exact version:
220
206
 
221
207
  ```
222
- GET /v1/dam/assets/{assetId}/versions/{versionId}/preview?width=512
223
- GET /v1/dam/assets/{assetId}/versions/{versionId}/download
208
+ GET /v1/dam/assets/{assetId}/versions/{versionId}/preview
224
209
  ```
225
210
 
226
- The preview takes the same transform parameters as query strings and
227
- streams the transformed image with `Cache-Control: private, no-store`.
228
- Use these for editorial UIs; never for site rendering.
211
+ It answers `302` to a short-lived signed URL: for JPEG, PNG, GIF, and WebP
212
+ up to 20 MiB, a WebP of that version downsampled at upload (longest edge
213
+ 1024 px, never upscaled); for SVG, formats the server does not decode
214
+ (HEIC, AVIF, TIFF, BMP), or a version whose preview is not generated yet,
215
+ the original. Follow the redirect without forwarding your bearer token. It
216
+ takes no shaping parameters. Use it for editorial UIs and thumbnails,
217
+ never for site rendering.