This service provides dynamic image transformations (resize, crop, format conversion, color adjustments, etc.) via a single URL-based API. It is designed to be called directly from front-end applications or other services.
API reference (Swagger): Swagger UI
Endpoints
Path prefixes (/ats and /its)
/ats is the canonical path prefix; /its is the legacy prefix and is still accepted for backwards compatibility. Both resolve to the same asset. A bare /{hash} without a prefix also reaches the service, but cache invalidation only clears the prefixed paths, so always build URLs with /ats.
A request to the legacy /its/{hash} path is redirected to the canonical /ats/{hash}. To avoid a chain of redirects, this is combined with the default-domain redirect (see Domain authorization) into a single hop: a request to https://alias.example.com/its/{hash} redirects straight to https://default.example.com/ats/{hash} in one step.
Cacheability depends on whether the hop crosses domains:
Redirect |
Status |
|
|
|---|---|---|---|
Prefix rewrite on the same domain |
|
relative ( |
|
Any hop to the default domain (with or without a prefix rewrite) |
|
absolute ( |
|
The cross-domain hop is deliberately not cacheable: its Location is derived from the request's host, so a shared cache that keys only on the URL would replay it for the default domain itself and loop the client (ERR_TOO_MANY_REDIRECTS). The same-domain rewrite has no host in its Location, so it stays correct for every domain it is replayed for and can be cached.
Image Transformation
GET {domain}/ats/{hash}
The {hash} is an opaque, encrypted token that identifies the asset. Store it and pass it back as it is; never build or decode one yourself.
Getting the hash:
Read it from the
id_hash_sfield of the asset's Solr document. An asset created through dam-api is given its hash when it is created.An older asset may not have one yet. Ask dam-api for it: getAssetIdHash (
GET /v4/assets/{assetId}/id-hash) for one asset, orgetAssetIdHashes(POST /v4/assets/id-hash/list) for up to 50. Both return the stored hash and create it if it is missing;id_hash_sfollows once the asset is reindexed.An asset has exactly one hash, and it does not change. Always use that one. A hash produced any other way can still load the image, but cache invalidation (see Freshness after a change in the DAM) only clears URLs built with the stored hash, so a withdrawn image would stay cached under any other.
Variant Redirect
GET {domain}/ats/{hash}/variant/{variantId}
A variant is an output a company defines once in the DAM: a size, from a crop dimension or its own width and height, and usually a format. Its id is the variantId (dam-api GET /v4/assets/predefined/variants/{variantId}). This endpoint works out the asset's crop for that variant and answers with a redirect to the Image Transformation endpoint:
302 Found Location: https://{defaultDomain}/ats/{hash}?crp=12.5,0,75,100&s=1080x1080&fo=jpg Cache-Control: no-cache
How to use it. There are two ways, and they differ in what happens when the crop changes in the DAM:
Use the variant URL itself, for example in an
<img>tag. Browsers and most HTTP clients follow the redirect on their own. Every load asks for the current crop, at the cost of one extra request; the redirect is never cached, the image it points to is.Follow it once and store the
Location. That URL is an ordinary transformation URL, so it is served without the extra hop. It keeps the crop it was made with: when someone later changes the crop in the DAM, a stored URL still shows the old one.
To download a variant, add d=true to the URL in Location (…&fo=jpg&d=true). The variant URL itself ignores every query parameter, so /variant/{variantId}?d=true gives the same redirect as without it.
What the redirect contains:
Parameter |
Value |
|---|---|
|
The crop. A custom crop saved in the DAM for this asset and variant is used as it is. Otherwise it is the largest region with the variant's aspect ratio, centred on the asset's focus point ( |
|
The variant's size. The crop has the variant's aspect ratio, so the image comes back at exactly this size |
|
The variant's format. Left out when the variant has none, and the output format then follows Output format |
The image is made like any request without d=true: from the asset's rendition, with the crop rescaled onto it (see How Source Selection Works). The rendition is at most 4000 pixels on its longer side, so a variant larger than the cropped part of it is upscaled.
Domains. The variant URL may be called from any approved domain (it does not have to be the default) and with either the /ats or the /its prefix; it is not subject to the default-domain / its→ats redirect (see Domain authorization). The Location already targets the customer's default domain and the canonical /ats prefix, so the client reaches the image in a single hop. An unapproved domain is a 403, as for the image itself.
Errors:
Status |
Cause |
|---|---|
|
The asset, the variant or the variant's crop dimension does not exist, or the asset is not available. The body is the same uniform one as for an image (see Asset availability) |
|
The variant has no size (neither a crop dimension nor a width and height), or no crop is saved and the asset's dimensions are not known |
|
The requesting domain is not approved |
After a change in the DAM. A changed custom crop or focus point produces a different crp, so a different image URL, and nothing has to be purged. The redirect itself can keep returning the previous crop for up to 24 hours, because variants, custom crops and focus points are cached (see Settings read from the DAM).
Query Parameters
Parameter |
Name |
Description |
Example / Format |
|---|---|---|---|
Quality |
|
Output quality (1–100) |
|
Width |
|
Fixed output width in pixels |
|
Height |
|
Fixed output height in pixels |
|
Size |
|
Width and height combined; both are required ( |
|
Relative Width |
|
Width as percentage of original |
|
Relative Height |
|
Height as percentage of original |
|
Relative Size |
|
Width/height as percentages ( |
|
Max Width |
|
Maximum output width in pixels |
|
Max Height |
|
Maximum output height in pixels |
|
Max Size |
|
Max width and height ( |
|
Background |
|
Background color (see Colors) |
|
Color |
|
Color for operations such as SVG recoloring |
|
Fit |
|
How the image fits the dimensions (default: |
|
Format |
|
Output image format (see Supported Formats). Without it, the output keeps the source's format — see Output format |
|
Color Profile |
|
Output color profile |
|
Padding |
|
Add padding around the image (pixels) |
|
Relative Padding |
|
Percentage-based padding; opposite sides must add up to less than 100% |
|
Crop |
|
Absolute pixel crop: left, top, width, height, in whole, non-negative pixels |
|
Relative Crop |
|
Percentage-based crop |
|
Blur |
|
Apply blur (radius, or radius + sigma), each 0.3–1000. With one value it is the blur strength |
|
Border |
|
Add a border: sides in pixels, then an optional color |
|
Relative Border |
|
Percentage-based border |
|
Rotation |
|
Rotate image in degrees (−360 to 360) |
|
Trim |
|
Trim transparent/empty edges |
|
Opaque |
|
Force opacity using a background color |
|
Strip |
|
Remove all metadata (EXIF, XMP, IPTC, ICC) — see Metadata |
|
No ICC |
|
Drop only the ICC colour profile, keeping other metadata |
|
Sampling Factor |
|
Chroma subsampling for JPEG output: |
|
Subtype |
|
Only |
|
Page Number |
|
Page to render for multi-page PDFs (default: |
|
FTE ID |
|
Use the file from |
|
Download |
|
Set |
|
Custom |
|
Semicolon-separated sequence of image operations (see Custom Parameter) |
|
Some parameters set the same thing two ways, and the combined one wins: s overrides w and h, sr overrides rw and rh, and ms overrides mw and mh. Two-value forms read vertical then horizontal: p=10x20 and bo=10x20 both put 10 on the top and bottom and 20 on the left and right.
Colors
bg, c, op and the color part of bo and rb accept a CSS color name (red), hex (#FF0000, #F00, or with alpha #FF000080), or rgb()/rgba(). In a URL, # must be encoded as %23; hex without it (FF0000) is accepted too. A color the conversion engine cannot read is a 400, except on the ImageMagick engine (see Supported Formats), which renders an unknown color name as black.
Custom Parameter
The custom parameter lets you specify an ordered sequence of image operations. Operations are executed in the order they appear.
That includes settings. As standalone parameters, fi and bg apply wherever they appear in the URL; inside custom, a setting only affects the operations after it. So put fi and bg first: custom=fi:fill;bg:%23FF0000;s:200x100;p:10. Written as custom=s:200x100;fi:fill;p:10;bg:%23FF0000, the resize uses the default fit and the padding gets the engine's default color, with no error. The ImageMagick engine applies fi wherever it appears, so a string in the wrong order can even give a different size depending on fo.
The service passes custom to the conversion engine without validating it, so a malformed operation is not caught as a 400 the way the standalone parameters are. The exception is a crop it rescales onto the rendition (see below): that one is checked like the cr and crp parameters, so a cr that is negative or not whole pixels, or a crop with no width or height, is a 400 there. An operation the engine cannot read at all, such as cr:abc, is skipped: the image comes back without it, as a 200. One it reads but cannot apply usually fails the conversion with a 500, or a 400 when the engine itself rejects the value. A cr that is negative or not whole pixels and is not rescaled (for example with d=true or fteId, when the original itself is converted, or after a resize) is not checked by the service, so the answer depends on the engine: Sharp fails a fractional one with a 500; otherwise the engine clamps the crop to the image and answers a 200 (ImageMagick for a fractional or negative one, Sharp for a negative one), or a 400 when none of the crop lies inside the image.
Format: key:value;key:value — e.g. custom=cr:30x50,1000x1000;ms:800x1000
custom converts the same file as any other request (see How Source Selection Works): the asset's pre-transcoded rendition, unless d=true asks for the original, fteId names another file, the asset is a PDF and a page is used, or the asset is an SVG, whose original vector is converted. The rendition is at most 4000 pixels on its longer side, so without a size in custom the output of a larger original is smaller than with d=true.
custom is written in the original's pixels, so on the rendition the service rescales a pixel crop (cr) and adjusts a relative crop (crp) that come before any resize, as it does for the cr and crp parameters. A crop after a resize is in output pixels and is left as it is. Without fo, the output keeps the original's format when an engine can write it (otherwise WebP), so it does not change with the file converted. An SVG asset follows the rule under Supported Formats.
The keys in the table below work on both engines. ImageMagick also reads bw (greyscale), cl, de, di, ilc, is, ni, sf, sh, st and stt (and their long names); a custom containing one is converted by ImageMagick, which reads the rendition and the original alike. Sharp would skip them.
All standard transformation parameters are ignored except:
Honored alongside |
Notes |
|---|---|
|
Applied as the output format |
|
Controls |
|
Selects the PDF page to use as source |
|
Selects the source file from |
|
Removes all metadata from the output |
|
Drops the ICC colour profile, keeps other metadata |
|
Sets chroma subsampling |
Available Operation Keys
Key |
Operation |
Format |
|---|---|---|
|
Quality |
|
|
Width |
|
|
Height |
|
|
Size |
|
|
Relative Width |
|
|
Relative Height |
|
|
Relative Size |
|
|
Max Width |
|
|
Max Height |
|
|
Max Size |
|
|
Background Color |
|
|
Color |
|
|
Fit |
|
|
Color Profile |
|
|
Padding |
|
|
Relative Padding |
|
|
Crop |
|
|
Relative Crop |
|
|
Blur |
|
|
Border |
|
|
Relative Border |
|
|
Opaque |
|
|
Rotate |
|
|
Trim |
|
Supported Formats
The service runs two conversion engines behind a single API and selects one automatically for each request — there is no parameter to choose an engine:
Sharp — the default (fastest) engine. Used whenever it can both read the source and write the requested output.
ImageMagick — the fallback engine. Used for conversions Sharp cannot perform (e.g.
bmp/heicinput or output, andarw/avif/dpx/jfif/psb/psd/tif/tiff/wbmpinput), and for acustomwith a key only ImageMagick reads.
For a given request Sharp is preferred; the request falls back to ImageMagick when Sharp cannot read the source format or cannot write the requested output format, and when custom contains a key only ImageMagick reads (see Custom Parameter) and ImageMagick can do the conversion. If neither engine can perform the conversion, the service returns 400 Bad Request.
Type |
Sharp (default) |
Additionally handled via the ImageMagick fallback |
|---|---|---|
Input |
|
|
Output |
|
|
The full set of accepted values is therefore:
Input:
arw,avif,bmp,dpx,gif,heic,jfif,jpeg,jpg,png,psb,psd,svg,tif,tiff,wbmp,webpOutput (
fo):avif,bmp,gif,heic,jpeg,jpg,png,tif,tiff,webp, andsvgfrom an SVG asset
Input-only formats — arw, dpx, jfif, psb, psd, wbmp — can be read but not produced as output.
SVG assets are converted from the original vector whenever the request transforms them, so a recolour (c) works and any size renders sharp; only a request without parameters streams the WebP rendition. The output stays SVG (fo=svg, or no fo) while every operation can be applied to the vector: every size (fixed, relative and maximum), crops, fi, bg, p, trim, c, q, cp, strip, noicc and sf, and in custom only c, q and cp. op, ro, bl, bo, rb and rp cannot be. Otherwise the output is PNG, labelled image/png.
The input list applies to the file actually converted. Without d=true or fteId, that is usually the asset's WebP rendition (see Output format; an SVG converts its original), so an asset whose original is in no list, such as eps, dng or a video, still converts. With them it is the original, and an original outside the list is a 400.
Converting a very large original this way, such as a multi-gigabyte psb, can take longer than the CDN in front of the service waits for an answer, currently 60 seconds. The request then fails with 504 Gateway Timeout. Transformations of the rendition are not affected.
PDFs are a special case: the service selects the appropriate high-res page preview image from the asset's
array_highres_ref_ssfield and uses that as the conversion input. Usepnto select the page (default:1).
Metadata
Embedded metadata — EXIF, XMP, IPTC and the ICC colour profile — is preserved by default on both engines, so a cropped or resized image keeps the source's copyright, credit and capture data.
Request |
Result |
|---|---|
No transformation parameters |
Source file streamed unchanged, with whatever metadata it carries. With |
Any transformation ( |
Metadata of the converted file carried into the output |
|
All metadata removed, including the ICC profile |
|
Only the ICC profile dropped; EXIF/XMP/IPTC kept |
Format capability still applies: gif cannot carry EXIF or IPTC, only XMP, and svg sources have no embedded metadata to preserve. A GIF's XMP, including its AI label, is carried into every output, and a GIF output carries the source's XMP, although neither engine reads or writes XMP in a GIF by itself.
Without d=true the converted file is usually the rendition (WebP, or GIF for a GIF asset), made when the asset was uploaded, so the output carries the rendition's metadata. A rendition is not made again later, so for older assets that can be less than the original has: an AI label declared only in a C2PA manifest is missing (or, for a GIF, garbled), and where the rendition was converted to another colour space, the rights metadata is missing too. Newer renditions carry both. Use d=true when the output must carry the original's metadata.
How Source Selection Works
For each request the service picks a source file in this priority order:
fteIdis set — uses thefile_reffromarray_fileswith the matchingfte_id. Returns400if not found.PDF — selects the requested page (
pn, default 1) fromarray_highres_ref_ss. Returns400if the page does not exist. A PDF requested with no transformation parameters and nopnstreams the original PDF instead.download=trueis set — usesoriginal_refdirectly (bypasses variant selection). A PDF never gets here: withd=trueplus a transformation orpnit takes step 2 and serves a page.SVG with a transformation — uses
original_ref, the vector, as described under Supported Formats.Standard image — prefers a pre-transcoded IMAGE-3 variant (
fte_id = -3040for WebP,-3039for GIFs). Falls back tooriginal_refif no matching variant exists.
custom follows the same steps. When an IMAGE-3 variant is chosen and a cr or crp crop is requested, as a parameter or in custom before any resize, the crop coordinates are automatically adjusted to account for the variant's potentially different dimensions, preserving the intended crop region.
If no transformation parameters are provided (no w, h, cr, fo, etc.), the source file is streamed directly without invoking the conversion engine, improving latency. The source is fetched from the approved domain the request was matched to, or from the service's own configured URL when no domain was matched. It is never taken from the request headers, which would let a caller point the fetch at any host.
Output format
Without fo, the output keeps the format of the source file picked above, which is not necessarily the format of the asset's original:
A standard image is usually served from its pre-transcoded WebP rendition (step 5), so
?w=800on a JPEG asset returns WebP, and so does a bare/ats/{hash}. A GIF asset uses its GIF rendition.A transformed SVG asset converts its original and stays SVG while the operations allow it, otherwise PNG (see Supported Formats). A bare
/ats/{hash}still returns its WebP rendition.d=truestarts from the original file, so it keeps the original's format.customkeeps it too, whichever file it converts, when an engine can write that format.A PDF page comes in the format of its preview image, and
fteIdin the format of that file.
Set fo whenever the client depends on a specific format.
Response
Success: 200 OK with the image as the response body.
Headers:
Header |
Value |
|---|---|
|
MIME type of the output format (e.g. |
|
|
|
|
|
Asset's last modification date from Solr |
|
Asset's |
Output filename is derived from the DAM API asset display name (extension stripped and replaced with the output format). Falls back to the Solr filename field, then to the asset ID.
Domain authorization
Each asset belongs to a company that has a list of approved domains, one of which may be flagged as the default domain. Every transformation is served from the default domain so that it is cached in a single place — which is what invalidation, tracing and access regulation rely on.
On every asset request the service checks the requesting domain (from X-Forwarded-Host, falling back to Host) against the approved list. When a proxy has appended to X-Forwarded-Host, producing a comma-separated chain, the first (client) value is used — the chain as a whole is not a hostname and comparing it as one would reject a legitimate request.
Not in the approved list →
403 Forbidden.Approved but not the default domain →
302redirect to the default domain, combined with theits→atsprefix rewrite so the client is sent tohttps://{defaultDomain}/ats/{hash}in a single hop. Not cacheable (see the table under Path prefixes).Already on the default domain → the request is served. When the company has no domain flagged as default, the target is the service's own host if that is one of the company's domains, then a configured fallback list. If none of those match, the request is served on the domain it arrived on.
X-Forwarded-Hostnames another approved domain, but the connection's ownHostheader is already the default domain → the request is served, without a redirect, and a warning is logged. An intermediary rewriting that header would otherwise have us redirect the client to the URL it just asked for, endlessly.
The approved-domain list is cached for up to 24 hours (see Settings read from the DAM), so the check adds negligible latency, but a change to a company's domains can take that long to apply.
The variant endpoint is exempt from the redirect: it is allowed through from any approved domain and any prefix, and the redirect it returns already points at the default domain + /ats. If the requesting domain is not approved it still returns 403.
If the company-domains lookup fails or no domains are configured, the service fails open and serves the request.
Asset availability
Only assets that are approved are served. An asset is treated as not existing — 404 — when it
is in any status other than approved,
has expired (
valid_toin the past) or is not valid yet (valid_fromin the future), orhas a GDPR status of pending or rejected.
This applies from the moment the status changes, but a transformation already cached on CloudFront keeps being served until that cache entry is invalidated — see Freshness after a change in the DAM for what triggers that and how long it takes. Treat the endpoint as eventually reflecting a change, not instantly.
The response is the same in every case, including for an asset that never existed and for a hash that is not a valid one:
{ "status": 404, "error": "Not Found", "message": "Asset not found or not available", "path": "/abc123", "timestamp": 1760000000000 }
This is deliberate. Saying that an asset exists but was withdrawn would disclose why — that a person in the image rejected GDPR consent, say — to anyone still holding its URL. The specific reason is logged by the service instead, against the request's correlation id (x-amz-cf-id), so quote that id when asking someone to look into a particular image.
Error Responses
Status |
Cause |
|---|---|
|
Invalid parameters (e.g. a value of the wrong type such as |
|
The requesting domain is not one of the company's approved domains (see Domain authorization) |
|
Asset does not exist, or is not available (see Asset availability). The body is the same in every case |
|
The image transformation failed |
|
An upstream service (search, DAM, admin) failed |
|
The conversion took longer than the CDN waits, currently 60 seconds; in practice only a very large original (see Supported Formats) |
Error responses are application/json with the same fields:
{ "status": 400, "error": "Bad Request", "message": "Invalid w parameter. Could not read: abc", "path": "/abc123", "timestamp": 1760000000000 }
message explains a 400. A 5xx does not reach the client as this JSON: the CDN replaces it with its own HTML error page, so rely on the status code alone. The detail is logged by the service, against the request's correlation id (x-amz-cf-id).
Caching
Response Caching
Responses are cached at two levels:
Cache-Control: public, max-age=900, s-maxage=604800, stale-while-revalidate=86400, keyed on the full URL including every query parameter. The two lifetimes differ on purpose:
Browser — 15 minutes (
max-age). Deliberately short. A copy in someone's browser cannot be invalidated by anything the service does, so it is a risk budget, not a performance setting.CloudFront — 7 days by default (
s-maxage, set per environment withapp.cloudfront-cache-max-age), plus a day ofstale-while-revalidate. This is where the saving is, because it can be cleared: every change that alters the image purges it, usually within about a minute (see below). Expect a transformation to be re-computed rarely.
Any change in path or query parameters results in a separate cache entry.
An asset that expires is never cached past its expiry. When the asset has a valid_to, both lifetimes are shortened so neither reaches beyond it — an asset expiring in two hours is served with max-age=900, s-maxage=7200, stale-while-revalidate=0, one expiring in two minutes with max-age=120, s-maxage=120, and one that has already expired with no-store. The Expires header carries the same moment. This matters because max-age overrides Expires in every HTTP cache, so without the cap the 7-day entry would outlive the asset's own validity.
Caching is disabled (no-cache, no-store, must-revalidate) when d=true (download).
Freshness after a change in the DAM
A cached transformation is not re-fetched until it expires, so the service actively invalidates CloudFront when an asset changes in a way that makes the cached bytes wrong:
Change in the DAM |
Cached image is cleared |
|---|---|
Asset status leaves approved |
✅ |
GDPR consent leaves confirmed — rejected or back to pending |
✅ |
A new version is uploaded |
✅ |
A previous version is restored |
✅ |
The asset is moved to the trash or permanently deleted |
✅ |
Transcoding of that new version finishes |
✅ a second time, see below |
An expiry lapsing on its own |
❌ not needed — the entry is capped to expire with the asset |
Validity dates edited so the asset expires, or starts, inside the cached lifetime |
✅ |
Validity dates edited to a date further out than anything cached can survive |
❌ not needed |
Metadata edited (title, tags, focus point, crops) |
❌ image bytes are unchanged |
Expect the new image within a minute or so: the invalidation waits a few seconds for the DAM's search index to catch up, then CloudFront propagates it. A change to many assets at once, such as a bulk edit, can take up to a few hours: CloudFront runs only a limited number of invalidations at a time, so the rest wait and are retried. If the retries run out first, the old copy is served until its cache entry expires.
A version upload clears the cache twice. The original file is replaced before the event is published, but the smaller rendition the service prefers to serve is generated asynchronously afterwards — so the first invalidation can re-cache the old rendition, and a second one follows once that work finishes. For a large image the second pass can be a few minutes behind the first.
Editing the validity dates is handled, but only where it can matter. An entry cached before the edit was given its lifetime from the old dates, so shortening an expiry — or setting one where there was none — is cleared if the new expiry falls inside that lifetime. Moving an expiry further out than any cached copy can survive clears nothing, because nothing would be stale.
Settings read from the DAM
The service also caches DAM settings it reads on every request, for up to 24 hours:
A company's approved domains and which one is the default. A change can take up to a day to affect the redirect and the
403.Variants, their custom crops and an asset's focus point. The variant endpoint can resolve the previous crop for up to a day.
An asset's display name, used for the output filename.
Example Requests
# Resize to 800px wide, keep aspect ratio
GET {domain}/ats/{hash}?w=800
# Crop and convert to WebP at 90% quality
GET {domain}/ats/{hash}?cr=100,100,400,400&fo=webp&q=90
# Fit into 500×500 box with a white background
GET {domain}/ats/{hash}?s=500x500&fi=contain&bg=FFFFFF
# Download the original file as an attachment
GET {domain}/ats/{hash}?d=true
# Custom sequence: crop then cap at 800×1000
GET {domain}/ats/{hash}?custom=cr:30x50,1000x1000;ms:800x1000
# Render page 2 of a PDF as WebP
GET {domain}/ats/{hash}?pn=2&fo=webp
# Redirect to the URL for variant 42 (resolves crop, size and format)
GET {domain}/ats/{hash}/variant/42
# Download variant 42: add d=true to the URL the redirect returns
GET {domain}/ats/{hash}?crp=12.5,0,75,100&s=1080x1080&fo=jpg&d=true
Comments
0 comments
Please sign in to leave a comment.