Repository navigation
Downloading
client.cdn fetches the actual bytes of a creation — image or video —
authenticating with your key, so it works for private creations too (needs the
read scope). Every method accepts a CDN id, a Creation, a QueueState, or a
generation result.
const path = await client.cdn.download(gen, { savePath: './out' });
// → './out.jpg' (extension inferred from the content type)
// shortcut on the client:
await client.download(gen, { savePath: './out' });const asset = await client.cdn.fetch(gen);
// { bytes: Uint8Array, contentType: 'image/jpeg', ext: 'jpg' }const blob = await client.cdn.toBlob(gen);
const url = URL.createObjectURL(blob);
// or straight into an <img>:
img.src = await client.cdn.toDataUrl(gen);Pass a transform and the CDN resizes the asset before it sends the bytes —
nothing is decoded or re-encoded on your side, so a grid of previews costs a
fraction of a grid of full assets. On a 2496×1664 generation measured against
production: 1164 kB untouched, 132 kB at maxSide: 512, 47 kB at maxSide: 256.
// A preview: `maxSide` scales the longest edge and keeps the aspect ratio,
// so it works for portrait and landscape without knowing which you got.
const preview = await client.cdn.fetch(gen, { maxSide: 512 });
// Transforms compose with savePath — a thumbnail written straight to disk.
await client.cdn.download(gen, { maxSide: 256, savePath: './thumb' });
// And with abort: the signal now travels in the same options object.
const ctrl = new AbortController();
const blob = await client.cdn.toBlob(gen, { maxSide: 512, signal: ctrl.signal });maxSide (?s) is the one you want for previews, thumbnails and contact sheets.
width (?w) and height (?h) target those axes specifically, which means the
result can be cropped to fit — reach for them only when a layout needs an exact
dimension. Sending maxSide alongside either is pointless — the CDN gives ?s
precedence over ?w / ?h.
Values must be finite and greater than zero, or the call throws
ApiError('INVALID_TRANSFORM') before any request leaves the process — a NaN
slipping in from a parsed query string fails at your call site instead of coming
back as a CDN error. Fractions are rounded to whole pixels.
fetch, toBlob and toDataUrl still take a bare AbortSignal as their second
argument, so code written before transforms existed keeps working unchanged:
const asset = await client.cdn.fetch(gen, ctrl.signal); // still validclient.cdn.url(gen); // 'https://dapp.2dai.io:444/cdn/file/<id>'
client.cdn.url(gen, { maxSide: 512 }); // '…/cdn/file/<id>?s=512'This raw URL loads directly in an <img> only for public creations. Private
creations require the key, so use fetch / toBlob / toDataUrl for those —
they accept the same transform.
The same methods work for both — the returned contentType (and ext) tell
them apart (image/jpeg → jpg, video/mp4 → mp4, …).
const vid = await client.generate.video({ prompt: '…', inputCreationId: gen.creationId });
const asset = await client.cdn.fetch(vid);
console.log(asset.contentType); // 'video/mp4'
await client.download(vid, { savePath: './clip' }); // → './clip.mp4'