Image Transformations
Freedam serves resized and re-encoded renditions of image assets at one path, /i/{options}/{gaid}. The URLs the API returns are signed for one user: you use them unchanged. You cannot build or modify one yourself, because the signature covers the whole URL, including the options.
Unlike the /api/v1/... endpoints, these URLs take no API token and are not counted against your API rate limit. The signature in the URL is the credential. They have their own throttle (see Rate limiting below) and are served from the tenant host.
The Freedam application uses the same path without a signature: those URLs act for the person signed in to the workspace and do not work outside a Freedam session.
Getting image URLs from the API
GET /v1/assets and GET /v1/assets/{gaid} return a media_urls object for each asset (see API Endpoints). Its thumbnails value holds one signed URL per rendition tier:
{
"media_urls": {
"thumbnails": {
"w_5120": null,
"w_4096": null,
"w_2048": "https://{workspace}.freedam.io/i/w=2048/IM-20260309-UDXZH8PP6VPW?expires=…&uid=42&v=…&signature=…",
"w_1024": "https://{workspace}.freedam.io/i/w=1024/IM-20260309-UDXZH8PP6VPW?…",
"w_512": "https://{workspace}.freedam.io/i/w=512/IM-20260309-UDXZH8PP6VPW?…",
"w_256": "https://{workspace}.freedam.io/i/w=256/IM-20260309-UDXZH8PP6VPW?…",
"w_128": "https://{workspace}.freedam.io/i/w=128/IM-20260309-UDXZH8PP6VPW?…"
},
"base_preview": "https://{workspace}.freedam.io/assets/preview/IM-20260309-UDXZH8PP6VPW/base_preview?…",
"original": "…",
"hls_master": null,
"converted_pdf": null,
"download": "…"
}
}
Throughout this page {workspace} stands for your own workspace subdomain.
- Every tier key is always present. A tier is
nullwhen the token's user may not receive it: sizes up tow_2048need the right to preview the asset, larger sizes also need the right to download that size. In the example the user may preview but not download. thumbnailsisnullwhen the user may not preview the asset, for audio, and for any asset that has no rendition: an asset still being processed, one whose preview generation failed, a file type with no preview, or an animated image that skips preview generation.base_previewisnullin the same cases. Show a placeholder, or useoriginalordownload.- Each URL is valid for 1 to 2 hours. The expiry is rounded to the hour, so the URL of a tier stays the same between two requests made within the same hour, which lets browsers cache the image. After expiry the URL answers
403. Read the asset again to get new URLs. Do not store them. - Each URL is bound to the token's user through
uid. That user's access to the asset, preview and download rights and watermark policy are checked again on every request, so a URL stops working when the user loses access or is deactivated. vchanges when the asset or the user's watermark changes, so a new rendition gets a new URL.
A bare GET is all it takes:
curl -o thumbnail.webp -H "Accept: image/webp" \
"https://{workspace}.freedam.io/i/w=512/IM-20260309-UDXZH8PP6VPW?expires=…&uid=42&v=…&signature=…"
Response
The response is the binary image, served with:
Content-Type:image/jpeg,image/png,image/webp, orimage/avif, depending on the format served.Cache-Control:private, max-age=3600. Shared caches and CDNs must not reuse the response across users.ETag: a validator that changes with the asset, the options, the format and the watermark. A request with a matchingIf-None-Matchreturns304 Not Modified.Vary: Accept, because the format depends on theAcceptheader.X-Transform-Cache: hit|miss|thumbnail|fallback, telling whether a stored copy of the rendition served the request.
The image is never enlarged: an original smaller than the requested width is served at its real size.
The user's current watermark policy is applied on every request: up to w_2048 the preview watermark, above it the download watermark.
Format negotiation
The URLs returned by the API carry no f= option, so the server reads the request's Accept header in this order:
image/avif: encodes AVIF.image/webp: encodes WebP.- otherwise: encodes JPEG.
Send the Accept header that matches what your client can display. A browser <img> tag does this by itself.
When an AVIF rendition is not ready yet, the server sends a WebP image at once and encodes the AVIF in the background. That response has Content-Type: image/webp, X-Image-Fallback: 1 and Cache-Control: private, max-age=5, must-revalidate, so the next request gets the AVIF.
Options syntax
options is a comma-separated list of key=value pairs. The API only issues w=… URLs; the other keys are listed so you can read the URLs Freedam issues elsewhere. Changing any of them invalidates the signature. Any other key, or a value outside its range, answers 400.
| Key | Meaning | Range / values | Default |
|---|---|---|---|
w |
Target width in pixels | integer 1–5120 |
source width (after fit) |
h |
Target height in pixels | integer 1–5120 |
source height (after fit) |
q |
Encoder quality | integer 1–100 |
85 |
f |
Output format | jpeg | jpg | png | webp | avif |
negotiated from Accept |
fit |
Resize strategy | cover | contain | fill |
contain |
dpr |
Device-pixel-ratio multiplier | 1, 2 or 3 |
1 |
fit semantics
contain(default): preserve aspect ratio, no crop. The result fits insidew × h.cover: preserve aspect ratio, crop the overflow so the output exactly fillsw × h.fill: stretch to exactlyw × h, ignoring aspect ratio.
If only one of w or h is given, the omitted axis is computed from the source aspect ratio.
Rendition tiers
Freedam stores renditions at fixed sizes (128, 256, 512, 1024, 2048, 4096, 5120 px). A request picks the tier for its long edge (dpr multiplies the requested edge first, so w=800,dpr=2 picks the 2048 tier), then reads the smallest stored rendition at least that large and resizes down. When none is stored yet it reads the largest smaller one.
The tier also decides which right is checked. Up to 1024 px, the right to preview the asset. At 2048 px, the right to preview it when the user may also download that size without a watermark; otherwise the right to download that size, with the download watermark. Above 2048 px, the right to download that size.
Errors
| Status | Meaning |
|---|---|
400 |
An option is unknown, out of range or malformed. |
403 |
The signature is invalid or expired, the URL was changed, the request is not on the tenant host, the user in uid is deactivated or may no longer preview the asset, or the size needs a download right the user lacks. |
404 |
The asset does not exist, or it has no rendition. |
429 |
Transform rate limit hit (see below). |
500 |
The image encoder failed. The request is logged. |
A 403 usually means the URL has expired or the user's access changed after the URL was issued. Read the asset again through the API to get a new URL.
Rate limiting
Image URLs use their own limit: 10 requests per minute for each combination of client IP address, asset GAID and options (the application's own requests count per signed-in user instead). A browser that has cached the response does not send the request again, so it does not count against the limit.
The /api/v1 token budget documented in Rate Limiting is independent of this throttle.
Tenant context
Image URLs must be requested on the tenant host that owns the asset (for example https://{workspace}.freedam.io/i/..., not the central or marketing domain). The URLs returned by the API already use that host.
Published image URLs
Images published through a publication channel have their own URLs, for websites and CMSs. You don't get them from the API: an administrator sets up the channel and people copy the links from the asset page.
https://{workspace}.freedam.io/i/w={width}/{gaid}/{channel key}/{signature}
- No expiry, no user. The signature covers the channel and the asset, not the options, the user or a date. The URL works as long as the asset is in the channel.
- Options. Only
wandfare accepted.wmust be one of the rendition tiers up to the channel's largest size (128,256,512,1024,2048,4096or5120) and defaults to the largest.ftakes the values listed above; without it the format is negotiated fromAccept. Any other key or width answers400. - Caching. Responses carry
Cache-Control: public, max-age=300, s-maxage=300andVary: Accept, so a CDN may cache each size and format for 5 minutes. - CORS. Any origin may load the images.
- Revocation. When the asset leaves the channel's conditions, its rights stop allowing the channel's use, the channel is turned off, or an administrator replaces the channel's links, the URL answers
404. Cached copies may show for up to 5 minutes. - Rate limit. 600 requests per minute for each channel and client IP address.
Other asset delivery URLs
media_urls also holds signed URLs that are not transforms. You don't construct these; you just follow them.
| URL shape | Purpose | Notes |
|---|---|---|
/assets/preview/{gaid}/{resolution} |
Serve a stored rendition inline. | Signed, valid 1 hour. Returned as media_urls.base_preview (the largest stored preview image), media_urls.converted_pdf (the PDF rendition of an Office document) and, for files other than video, audio and PDF, media_urls.original. |
/assets/download/{gaid}/{resolution} |
Force a download with Content-Disposition: attachment. |
Signed, valid 1 hour. Returned as media_urls.download, with resolution=original. The user's download rights and watermark policy are checked when the URL is followed. |
/assets/download-prepare/{gaid}/{resolution} |
Landing page that starts the download with JavaScript. | Signed. Used in the emails Freedam sends, so the recipient sees a "Your download has started" page. Not returned by the API. |
/assets/stream/{gaid}/master.m3u8 |
HLS master playlist for video assets. | Signed, valid 6 hours. Returned as media_urls.hls_master when the video has an HLS rendition, otherwise null. Variant playlists and segments are signed separately, also for 6 hours. |
/assets/stream/{gaid}/subtitles/{language}.m3u8 |
HLS subtitle playlist for a given language tag. | Signed. Referenced by the master playlist. The language tag is 1 to 16 characters of [A-Za-z0-9_-]. |
/embed/{shareToken}/{gaid} |
Public video embed for <iframe>. |
Tokens are created with /api/v1/assets/{gaid}/embed. |
For video, audio and PDF assets, media_urls.original is a direct URL to the stored file, so it can be used as the source of a <video>, <audio> or <embed> tag. On workspaces with remote storage it is a presigned storage URL valid for 1 hour.
Pass every signed URL through to your <img>, <a>, or <video> tag unchanged. Do not strip or reorder query parameters: doing so invalidates the signature.
Using image URLs in your own backend
Because the URLs are bound to the token's user and expire, treat them as short-lived values: read the asset when you render the page, or proxy and cache the image bytes on your side. With the TypeScript SDK:
const asset = await client.assets.get('IM-20260309-UDXZH8PP6VPW');
const thumbnails = asset.media_urls.thumbnails;
if (thumbnails === null) {
// No rendition: show a placeholder, or link to asset.media_urls.download
} else {
const response = await fetch(thumbnails.w_1024, {
headers: { Accept: 'image/webp' },
});
const bytes = await response.arrayBuffer();
}
Worked examples
Responsive <img> srcset. Use the tiers from media_urls.thumbnails and let the browser pick one. The format is negotiated from the browser's Accept header:
<img
src="{thumbnails.w_1024}"
srcset="
{thumbnails.w_512} 512w,
{thumbnails.w_1024} 1024w,
{thumbnails.w_2048} 2048w
"
sizes="(max-width: 600px) 100vw, 800px"
alt="…"
/>
Replace each {thumbnails.…} placeholder with the matching URL from the API response when you render the page.
Small thumbnail for a card grid. Use thumbnails.w_256 or thumbnails.w_512 and size the image with CSS (object-fit: cover for a square crop).
Full-size image. Use the largest non-null tier in thumbnails (w_5120 when the user may download that size, else w_2048), or media_urls.download for the original file.