DocgenTry it

Signed downloads

Rendered outputs are stored on disk and served via Laravel's temporarySignedRoute. The URL carries a signature and an expiry, so anyone with the link can download for the lifetime of the signature — and no longer.

What a signed URL looks like

https://api.docgen.philiprehberger.com/v1/renders/01HY.../outputs/pdf
  ?expires=1717891234
  &signature=a8f3c2b09e...

No Authorization header is needed on the GET. The signature is the auth. This means:

  • You can paste the URL into an email and it just works.
  • You can return it from your backend to a browser and the browser downloads directly — no proxy through your server.
  • You can attach it to a Slack message; the preview unfurls.

TTL

The signature expires. By default, signed URLs live for one hour. You can override per-request, up to the workspace ceiling (default 24 hours):

POST /v1/renders
{
  "template_id": "01HX...",
  "formats": ["pdf"],
  "data": { ... },
  "signed_url_ttl": 21600    // 6 hours, in seconds
}

Once a render succeeds, its outputs are stored. When you pollGET /v1/renders/{id}, the API mints a fresh signed URL each time, so subsequent calls always return a URL valid for the configured TTL from now — not from the original render submit time.

What happens at expiry

After expires passes, the URL returns:

401 Unauthorized
Content-Type: application/problem+json
{
  "type": "about:blank",
  "title": "Unauthorized",
  "status": 401,
  "detail": "Signed URL is missing or expired."
}

The file itself sticks around longer — there's a cleanup cron that sweeps expired files past their TTL plus a grace window. If you re-poll the render, you get a fresh signed URL pointing at the same file, until the cleanup runs.

Why not S3 + pre-signed URLs?

S3 is the right answer for production scale, and the codebase is structured to swap to it by changing one filesystem driver. For a portfolio demo, local disk + Laravel signed URLs proves out the same pattern without dragging in AWS account state. If you're building this for a real customer, S3 with the same signed-URL flow is a one-config-line change.

Security model — what signed URLs do and don't do

Do:stop accidental URL guessing. The signature is HMAC-SHA256 over the URL path + expiry + the app's secret key. Brute-forcing it is infeasible.

Don't: stop the URL from being shared. Anyone with the link can download for the lifetime of the signature. If you need recipient-bound URLs (only Alice can download), build a thin gateway in your app that resolves recipient → signed URL on demand instead of returning the signed URL directly.

Filename + MIME type

Downloads come with Content-Disposition: attachment; filename="render-{id}.{format}" and the appropriate MIME type:

  • application/pdf for PDF
  • application/vnd.openxmlformats-officedocument.wordprocessingml.document for DOCX
  • text/html; charset=utf-8 for HTML

Next

DOCX fidelity— what survives the HTML → DOCX conversion, what doesn't.