Skip to content

Delivery and signed links

Serve files from their stable link, resize and convert images with query parameters, understand caching after replacements, and share private files with revocable signed links.

On this page

This page covers what happens after a file is uploaded: how its stable link serves bytes, which query parameters resize and convert images, how long responses are cached, and how to give someone time-limited access to a private file with a signed link.

Delivery uses its own origin, https://cdn.steadylink.io, and never takes an API key. Managing signed links uses the API origin and a key like every other route.

The JavaScript examples assume const steadylink = new SteadyLink({ apiKey: process.env.STEADYLINK_API_KEY! }) and the Python examples assume client = SteadyLink(api_key=os.environ["STEADYLINK_API_KEY"]).

Endpoints#

OperationMethod and pathAccess
Deliver a fileGET or HEAD https://cdn.steadylink.io/a/{asset_id}Public, or a signed token for private files
Create a signed linkPOST /api/assets/{asset_id}/signed-urlassets:write
List signed linksGET /api/assets/{asset_id}/signed-urlsassets:read
Revoke a signed linkDELETE /api/assets/{asset_id}/signed-urls/{grant_id}assets:write
Get a preview URLGET /api/assets/object/{asset_id}/previewassets:read
Purge cached image variantsPOST /api/assets/{asset_id}/purgeassets:write

Delivery#

Deliver a file#

GET/a/{asset_id}
No API key. Public route.

Serves the file's current revision, or a specific revision with v. Without parameters it returns the original bytes. With image parameters it returns a resized or converted variant, which is generated on the first request and stored for later ones. HEAD is also supported and returns the same headers without a body.

Query parameters

vinteger
Serve this revision number instead of the current one. Pinned responses are cached for a year. Omit it to follow the current revision.
tokenstring
Signed token from Create a signed link. Required for private files, ignored for public ones.
winteger
Target width in pixels, 1 to 8000. Alias: width.
hinteger
Target height in pixels, 1 to 8000. Alias: height.
fitstringDefault contain
cover scales and crops to exactly w by h. contain, inside, and outside are accepted and currently behave the same: the image is scaled down to fit within the box, keeping its aspect ratio, and is never enlarged.
fmstring
Output format: webp, jpg (or jpeg), or png. Alias: format. When an image is resized without fm, the result is JPEG, so set fm=png or fm=webp to keep transparency.
qintegerDefault 82
Output quality for JPEG and WebP, 30 to 95. Values outside the range return 400. Alias: quality. On its own, q does not create a variant; combine it with w, h, or fm.
dprnumberDefault 1
Device pixel ratio, 0.1 to 3. Without w and h it scales the original, for example dpr=0.5 halves it. With w or h it does not change the output size: request the pixel width you need directly, such as w=1200 for a 600-pixel slot on a 2x screen. w or h multiplied by dpr must stay within 8000.
allowUpscalebooleanDefault false
For fit=cover, allow the output to exceed twice the source dimensions. Without it, cover output is capped at 2x the source width and height. Other fits never enlarge. Accepts true, false, 1, 0, yes, no.
focusstringDefault center
auto picks a high-detail area to keep when cropping. Requires fit=cover.
fp-x, fp-ynumber
Explicit focal point, each from 0 (left or top) to 1 (right or bottom). Send both, with fit=cover, and without focus=auto. Aliases: focal-x, focal-y.
presetstring
Apply a named public transform preset from Dashboard > Developers > Transforms. Cannot be combined with other image parameters except v.
showcaseflag
Return a minimal HTML page that displays the image. Images only, and not combinable with image parameters.
ref, utm_source, utm_medium, utm_campaign, utm_content, utm_termstring
Attribution parameters. They do not affect the response and are recorded as traffic sources.
Examples
# Current revision, original bytes
https://cdn.steadylink.io/a/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71

# Revision 3, forever
https://cdn.steadylink.io/a/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71?v=3

# 1200 x 630 social card, cropped around the most detailed area, as WebP
https://cdn.steadylink.io/a/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71?w=1200&h=630&fit=cover&focus=auto&fm=webp

# 1200 pixels wide (a 600-pixel slot on a 2x screen) as WebP at quality 75
https://cdn.steadylink.io/a/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71?w=1200&fm=webp&q=75

# Private file with a signed token
https://cdn.steadylink.io/a/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71?token=v2.3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71.1791472980.1c8e5a3f-2d7b-4a9c-b6e1-8f0d2c4a6b97.4b1e...

Build links in code rather than by hand:

const url = steadylink.link("3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71", {
  width: 1200,
  height: 630,
  fit: "cover",
  focus: "auto",
  format: "webp",
});

Image parameter rules#

  • A variant is created only when w, h, fm, or a dpr other than 1 is present. Other image parameters on their own return the original bytes.
  • Image parameters work only on raster images: JPEG, PNG, WebP, GIF, BMP, and TIFF. On any other file they return 415 Transforms supported only for raster images. SVG and PDF are served as-is.
  • At most eight image parameters per request, not counting v, token, and attribution parameters.
  • Each parameter may appear once, and a short name cannot be combined with its alias (w with width, fm with format). Either returns 400.
  • Unknown parameters return 400 Unknown params: .... A typo fails loudly instead of silently serving the original.
  • When both w and h are set, their ratio must be between 1:50 and 50:1.
  • With fit=cover and no fp-x, fp-y, or focus=auto, the file's saved focal point is used if one is set. See Set a focal point.
  • Each file can have up to 100 distinct image variants and each workspace up to 10,000 by default. A request for a new variant beyond that returns 429 with asset_transform_cardinality_exceeded or workspace_transform_cardinality_exceeded. Variants that already exist keep working. Use a small, fixed set of sizes rather than arbitrary widths.

Delivery responses#

StatusWhen
200The file or variant.
206A byte range, when the request sends Range and asks for the original bytes. Use it for video and resumable downloads.
304The request's If-None-Match matches the ETag.
400An invalid, duplicate, conflicting, or unknown parameter. The message names the problem.
403Forbidden: the file is private and the token is missing, expired, revoked, for another file, or for another revision. Blocked: infected: the current revision failed its malware scan.
404No such file, a v that does not exist, or a file requested through a custom domain that belongs to another workspace.
415Image parameters on a file that is not a raster image, or showcase on a file that is not an image.
423Scanning: the revision's malware scan has not finished. Retry after a short wait.
429Too many distinct image variants, or too many requests from one client.
451The workspace's delivery policy blocks the requester's country.

Delivery response headers#

HeaderValue
Cache-Control, CDN-Cache-ControlSee Caching.
ETagSHA-256 of the bytes served. For the original, it equals the revision's hash.
Content-TypeThe stored type, or the output type of a variant.
Content-Dispositioninline with the stored filename. HTML and SVG files are served as attachment (downloaded, not rendered) unless the bucket allows inline rendering.
Accept-Rangesbytes for original files.
Surrogate-Key, Cache-Tagasset-{asset_id}, for targeted purges at an edge cache.
Content-Security-Policy, X-Content-Type-OptionsA strict policy and nosniff, so a hosted file cannot run scripts against your visitors.

Caching#

The cache lifetime depends on whether the URL follows the current revision or pins one:

URLCache-ControlMeaning
Stable link, no vpublic, max-age=300, stale-while-revalidate=3600, stale-if-error=3600, no-transformCaches may reuse a response for five minutes, then must check again.
Pinned with ?v=public, max-age=31536000, immutable, stale-while-revalidate=604800, stale-if-error=604800, no-transformA revision's bytes never change, so caches keep it for a year.
Private fileprivate, no-store, max-age=0Never stored by shared caches, so revoking access takes effect on the next request.

What this means after a replacement or rollback: SteadyLink itself switches to the new current revision immediately, but browsers and caches that already hold the stable link may serve the previous bytes until their five minutes are up. stale-while-revalidate allows a cache to answer one more request with the old copy while it fetches the new one. Plan on up to about five minutes before every viewer sees a change.

When a page must show a specific revision the moment it ships, link to the pinned URL (?v=4) in that release. When a page should always show the latest file, use the stable link and accept the short delay.

A signed link is a stable link with a token parameter that opens a private file until it expires or you revoke it. Each one is backed by a grant record you can list and revoke, so you can share a file with one customer, see which links exist, and cut access off without touching the file.

POST/api/assets/{asset_id}/signed-url
Requiresassets:write

Query parameters

ttlintegerDefault 300
Lifetime in seconds. Values are clamped to the range 60 (one minute) to 2,592,000 (30 days).
namestring
Internal label shown when listing grants, such as Acme review. Trimmed and cut to 200 characters.
revisioninteger
Limit the grant to one revision number. Omit it to follow the current revision. An unknown revision returns 404 Revision not found.
curl -X POST "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/signed-url?ttl=86400&name=Acme%20review" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
201 CreatedResponse
{
  "id": "1c8e5a3f-2d7b-4a9c-b6e1-8f0d2c4a6b97",
  "token": "v2.3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71.1791472980.1c8e5a3f-2d7b-4a9c-b6e1-8f0d2c4a6b97.4b1e9f0c7a2d...",
  "name": "Acme review",
  "revisionNumber": null,
  "expiresAt": "2026-10-09T15:23:00.118204"
}

Append the token to the delivery URL as ?token=.... Image parameters can be combined with it, for example ?token=...&w=800&fm=webp.

Things to know:

  • The token is returned once. Listing grants does not return tokens. Store the URL if you need to send it again, or create a new grant.
  • Revision grants need v. A grant created with revision=3 only opens ?v=3&token=.... The same token without v, or with another v, returns 403. The SDK's signed.url does not add v for you; use steadylink.link(assetId, { token: signed.token, version: 3 }).
  • Public files ignore tokens. A signed link to a public file works, but so does the plain link. Make the file private to restrict it. See Set file visibility.
  • Bucket grants cover the bucket. A grant created on a bucket ID also opens every private file in that bucket. Create grants on file asset IDs unless you mean to share the whole bucket.
  • Creating a grant is an expensive operation and counts toward the expensive-operation limits in Plan access.
GET/api/assets/{asset_id}/signed-urls
Requiresassets:read

Returns the 200 most recent grants for a file, newest first, including expired and revoked ones. Tokens are never included.

curl "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/signed-urls" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{
  "items": [
    {
      "id": "1c8e5a3f-2d7b-4a9c-b6e1-8f0d2c4a6b97",
      "name": "Acme review",
      "revisionNumber": null,
      "expiresAt": "2026-10-09T15:23:00.118204",
      "revokedAt": null,
      "createdAt": "2026-10-08T15:23:00.118204"
    }
  ]
}

A grant is active when revokedAt is null and expiresAt is in the future. Timestamps are UTC without an offset.

DELETE/api/assets/{asset_id}/signed-urls/{grant_id}
Requiresassets:write

Stops a signed link before it expires. Private responses are never cached, so the next request with that token returns 403.

curl -X DELETE "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/signed-urls/1c8e5a3f-2d7b-4a9c-b6e1-8f0d2c4a6b97" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "revoked": true }

A grant ID that does not belong to this file returns 404 Signed access grant not found. Revoking an already revoked grant succeeds again and updates revokedAt.

Other delivery tools#

Get a preview URL#

GET/api/assets/object/{asset_id}/preview
Requiresassets:read

Returns a short-lived URL to view a file without going through public delivery. Previews work for private files without a grant and are not counted in delivery analytics, which makes them right for admin tools and review screens.

Query parameters

vinteger
Revision to preview. Omit it for the current revision.
thumbnailbooleanDefault false
Return a 480-pixel-wide WebP thumbnail instead of the original. Images only.
curl
curl "https://api.steadylink.io/api/assets/object/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/preview?thumbnail=true" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "url": "https://storage-endpoint.example/transforms/e4d7b1a9-.../3b9c....webp?X-Amz-Expires=300&X-Amz-Signature=..." }

The URL expires after five minutes; request a new one each time you render. A revision that is not yet clean returns 409 Revision is not ready for preview. thumbnail=true on a non-image returns 415, and on an image larger than 64 MiB returns 413.

Purge cached image variants#

POST/api/assets/{asset_id}/purge
Requiresassets:write

Forgets every stored image variant of a file, across all its revisions. The next request for each size or format renders it again from the original. You rarely need this, because a new revision always gets fresh variants. It does not clear browser or edge caches.

curl
curl -X POST "https://api.steadylink.io/api/assets/3f2a9c1e-8b4d-4c7a-a1e2-6d5f0b9c3e71/purge" \
  -H "X-API-Key: $STEADYLINK_API_KEY"
200 OKResponse
{ "purged": true }

Purging counts as an expensive operation.

Next steps#