Freedam

Image Transformations

Freedam serves resized and re-encoded renditions of image assets through an image transformation proxy. Every transform URL is signed by Freedam for one user: you receive these URLs in API responses and use them unchanged. You cannot build or modify a transform URL yourself, because the signature covers the whole URL, including the options.

Unlike the /api/v1/... endpoints, transform 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 per-URL throttle (see Rate limiting below) and are served from the tenant host.

There are two transform paths:

Path Auth Cache Watermarking Where you get the URL
/ip/{options}/{gaid} Signed + uid Private Per-user preview policy media_urls.thumbnails of every asset in the API
/is/{options}/{gaid} Signed + uid Private Per-user download policy Issued inside the Freedam application, not by /api/v1

No public transform URL. The unsigned /i/{options}/{gaid} path no longer exists and answers 404. It served images without checking who asked. Use the signed URLs from media_urls instead.

Getting transform 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 /ip/ URL per rendition tier:

{
    "media_urls": {
        "thumbnails": {
            "w_5120": "https://{workspace}.freedam.io/ip/w=5120/IM-20260309-UDXZH8PP6VPW?expires=…&uid=42&v=…&signature=…",
            "w_4096": "https://{workspace}.freedam.io/ip/w=4096/IM-20260309-UDXZH8PP6VPW?…",
            "w_2048": "https://{workspace}.freedam.io/ip/w=2048/IM-20260309-UDXZH8PP6VPW?…",
            "w_1024": "https://{workspace}.freedam.io/ip/w=1024/IM-20260309-UDXZH8PP6VPW?…",
            "w_512": "https://{workspace}.freedam.io/ip/w=512/IM-20260309-UDXZH8PP6VPW?…",
            "w_256": "https://{workspace}.freedam.io/ip/w=256/IM-20260309-UDXZH8PP6VPW?…",
            "w_128": "https://{workspace}.freedam.io/ip/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.

  • thumbnails is null 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_preview is null in the same cases. Show a placeholder, or use original or download.
  • 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 policy and watermark policy are checked again on every request, so a URL stops working when the user loses access.
  • v changes when the asset is updated, 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/ip/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, or image/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 policy. A request with a matching If-None-Match returns 304 Not Modified.
  • Vary: Accept, because the format depends on the Accept header.
  • X-Transform-Cache: hit|miss on /ip/, telling whether the stored copy of the rendition served the request.

/ip/ never enlarges an image: an original smaller than the requested width is served at its real size.

Format negotiation

The URLs returned by the API carry no f= option, so the server reads the request's Accept header in this order:

  1. image/avif: encodes AVIF.
  2. image/webp: encodes WebP.
  3. 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, /ip/ serves 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.

Key Meaning Range / values Default
w Target width in pixels 1–5120 source width (after fit)
h Target height in pixels 1–5120 source height (after fit)
q Encoder quality 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–3 1

fit semantics

  • contain (default): preserve aspect ratio, no crop. The result fits inside w × h.
  • cover: preserve aspect ratio, crop the overflow so the output exactly fills w × h.
  • fill: stretch to exactly w × 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 transform reads the smallest tier at least as large as the requested long edge, then resizes down. dpr multiplies the requested edge before the tier is picked, so w=800,dpr=2 reads the 2048 tier, not the 1024 one.

Errors

Status Meaning
400 One of the options is out of range or malformed.
403 The signature is invalid or expired, the URL was changed, the request is not on the tenant host, or the user in uid may no longer preview the asset or needs a different watermark.
404 The asset does not exist, or it has no rendition.
429 Per-URL 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

Transform URLs use their own limit: 10 requests per minute for each combination of client IP address, asset GAID and options. 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

Transform URLs must be requested on the tenant host that owns the asset (for example https://{workspace}.freedam.io/ip/..., not the central or marketing domain). The URLs returned by the API already use that host.

Signed transforms: /is/ and /ip/

/ip/{options}/{gaid}: preview-class signed transform

GET /ip/w=1024/IM-20260309-UDXZH8PP6VPW?expires=...&uid=42&v=...&signature=...
  • signature covers the whole URL. Changing the path or any query parameter invalidates it.
  • uid identifies the user the URL was issued for. That user must be able to view the asset, and their access rules must allow a preview. Both are checked on every request. An asset in the Trash is only served to users who can open the Trash.
  • wm (optional) names the watermark the user's preview policy applies. If the policy requires a watermark and the URL carries none, or carries a different one, the request returns 403.
  • A URL without uid belongs to a Public Portal guest and only works while the Public Portal is enabled.

/is/{options}/{gaid}: download-class signed transform

GET /is/w=1600,q=85/IM-20260309-UDXZH8PP6VPW?expires=...&uid=42&wm=7&signature=...

Same options grammar, signature, throttle and Cache-Control: private, max-age=3600 as /ip/, but it checks the user's download policy: access to the asset, access to the resolution tier the request reads, and the download watermark. It can enlarge an image to the requested size. The /api/v1 endpoints do not return /is/ URLs.

If the user's watermark policy changed after the URL was issued, both paths return 403. Read the asset again to get a new URL.

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 transform 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 thumbnails.w_5120 for the largest rendition, or media_urls.download for the original file.

Try Freedam

Related Documentation

Explore more in this category