Signed URLs authenticate GET /v1/capture with a project signing secret instead of an API key. They are useful when a client should trigger a render directly but must not see your API key.

When to use signed URLs

Browser-triggered renders

Let a frontend request a pre-approved render URL without embedding a secret in the bundle.

Embeddable templates

Create short-lived render links for customer portals, CMS tools, or dynamic images.

Requirements

  1. Create or choose a project.
  2. Read the project signing secret from the dashboard or API.
  3. Mint a URL on your backend.
  4. Send only the signed URL to the browser.

The signing secret belongs on your server. Treat it like an API key.

Query parameters

Signed capture URLs include normal GET /v1/capture query parameters plus:

ParameterDescription
project_idProject id that owns the signing secret.
expiresUnix timestamp in seconds.
signatureHex HMAC-SHA256 signature over the canonical payload.

Canonical payload

Build the signature from:

txt
METHODPATHsorted=urlencoded&query=params&excluding=signature

For GET /v1/capture, the first two lines are:

txt
GET/v1/capture

Sort all query parameter pairs by their encoded key=value string and exclude signature.

JavaScript example

javascript
import crypto from "node:crypto";function signCaptureUrl({ secret, params }) { const url = new URL("https://screenframed.com/v1/capture"); for (const [key, value] of Object.entries(params)) { url.searchParams.set(key, String(value)); } const pairs = []; for (const [key, value] of url.searchParams.entries()) { if (key === "signature") continue; pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`); } pairs.sort(); const canonical = ["GET", url.pathname, pairs.join("&")].join("\n"); const signature = crypto.createHmac("sha256", secret).update(canonical).digest("hex"); url.searchParams.set("signature", signature); return url.toString();}const signedUrl = signCaptureUrl({ secret: process.env.SCREENFRAMED_PROJECT_SIGNING_SECRET, params: { project_id: "prj_01K...", expires: Math.floor(Date.now() / 1000) + 300, site: "https://example.com/pricing", device: "browser-macos", bg: "midnight", shadow: "soft", aspect_ratio: "16:9", },});

Payload query parameter

For complex payloads, encode the JSON body as a base64url payload query param. Explicit query parameters still override values inside payload.

json
{ "url": "https://example.com/pricing", "device": "browser-macos", "background_preset": "midnight", "shadow": "float"}

Rotation

Rotate the project signing secret if it is exposed or no longer trusted:

bash
curl -X POST "https://screenframed.com/v1/projects/prj_01K.../signing-secret/rotate" \ -H "Authorization: Bearer $SCREENFRAMED_API_KEY"
Ask a question... ⌘I