---
title: "Render a signed link"
method: GET
path: "/l/{link_id}/{filename}"
tags: ["Signed links"]
---

# Render a signed link

`GET /l/{link_id}/{filename}`

## Request

- Base URL: `https://img.dynamicdocumentapi.com`
- URL: `https://img.dynamicdocumentapi.com/l/{link_id}/{filename}`
- Auth: none required

The public image URL of a signed link, served from `https://img.dynamicdocumentapi.com` without an API key. Checks, in order: unknown, disabled or deleted link, or a format the link does not enable → 404 `not_found`; expired link or a signed `exp` in the past → 410 `link_expired`; `access: signed` needs `sig` = base64url(HMAC-SHA256(secret, "GET\n/l/{link_id}/{name}.{ext}\n" + canonical query without `sig`)) → 403 `invalid_signature` (open links accept only allowed parameters that have `param_limits`); unknown parameters or a value over `max_length` → 400 `validation_error`; a `Referer` whose host is not in `allowed_referrers` → 403 `referrer_not_allowed`; the per-IP hourly limit → 429 `rate_limited`; the plan feature → 402 `plan_feature_unavailable`; the link quota → 429 `quota_exceeded`.

 A cached result is returned with `X-Billed-Renders: 0` and creates no render; a cache miss renders the template's live version (`X-Billed-Renders: 1`, `X-Render-Id`). Responses carry `ETag` (304 on `If-None-Match`), `Cache-Control: public, max-age=…` (`private` for a link with `allowed_referrers` and in zero-retention workspaces: no shared cache keeps the file) and `Access-Control-Allow-Origin: *`. `HEAD` runs the same checks and never renders. When the render limit is reached, a link with a fallback image returns it instead of 402 `render_limit_reached`.

 Cloudflare's cache in front of the URL keeps `public` responses until their `max-age`, so a repeated URL can come from it (`CF-Cache-Status: HIT`, `Age`) with the headers of the response that filled it: `X-Billed-Renders` and `X-Render-Id` then describe that first response, and the hit is never billed. Changing what a link serves or deleting it, a change to its template's live version or settings, and suspending or deleting the workspace or turning on zero retention remove the copies from Cloudflare within seconds of the change reaching the region. Browsers keep theirs until `max-age`.

## Path parameters

- `filename` string, required
- `link_id` string, required

## Query parameters

- `exp` integer
- `params` object
- `sig` string

## Headers

- `If-None-Match` string

## Response `200`

The rendered file (PNG, JPEG, WebP or PDF per the extension).

## Other responses

- `304`: Not modified (`If-None-Match`).
- `400`: Parameter not allowed, over `max_length`, malformed or over 8 KB of query.
- `402`: `plan_feature_unavailable` or `render_limit_reached` (no fallback image).
- `403`: `invalid_signature` or `referrer_not_allowed`.
- `404`: Unknown, disabled or deleted link, or a format the link does not enable.
- `410`: `link_expired` (or the template was deleted).
- `422`: The render failed (template error).
- `429`: `rate_limited` (per IP and hour, `Retry-After`) or `quota_exceeded`.

---

[API](https://skmtc.dev/dynamicdocumentapi/apis/dynamic-document-api.md) · [All operations](https://skmtc.dev/dynamicdocumentapi/apis/dynamic-document-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dynamicdocumentapi/apis/dynamic-document-api/revisions/0f14bc0000f4?raw)
