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/pdffor PDFapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentfor DOCXtext/html; charset=utf-8for HTML
Next
DOCX fidelity— what survives the HTML → DOCX conversion, what doesn't.