Skip to content

Downloads & Storage

A rendered PDF lives in a private bucket. download_url is not a permanent address — it is a signed, time-limited link, minted fresh every time you read the job. Store the job id; resolve the link when you actually need the bytes.

  1. The bucket is private. There is no public URL for a rendered document.
  2. download_url is a presigned GET link, signed for exactly one object.
  3. It is generated on read and never persisted. Two reads of the same job return two different links to the same file.
  4. It expires — one hour by default.

Read the job whenever you need a working link:

Terminal window
curl https://api.tupsara.com/v1/pdf/a3f1c2e4-9b70-4c1e-8f2a-6d5b0e7c1a34 \
-H "Authorization: Bearer pdf_live_..."
{
"id": "a3f1c2e4-9b70-4c1e-8f2a-6d5b0e7c1a34",
"status": "completed",
"download_url": "https://.../a3f1c2e4.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=3600&X-Amz-Signature=...",
"file_size_bytes": 18420,
"render_duration_ms": 812,
"created_at": "2026-08-31T19:58:51Z"
}

The signature is computed locally, so asking for a link costs a database read — not a round trip to storage.

download_url, file_size_bytes and render_duration_ms are null until status is completed. A job that ends as failed keeps them null — there is no partial file to fetch. Branch on status, not on the presence of a URL.

Field Meaning
id UUID of the job. The only durable handle — this is what you store.
status pending, rendering, completed or failed.
download_url Signed link to the PDF. null until the render completes.
file_size_bytes Size of the rendered file.
render_duration_ms Time the browser spent producing this document.
created_at When the job was accepted, in UTC.

Full request/response reference: PDF.

An expired link is rejected by the object store, not by Tupsara — so you get its error, not the error envelope the API uses everywhere else. There is nothing to retry at that URL. Read the job again and use the new link.

Never hand a download_url to something that will use it later: a queued email, a stored bookmark, a cached client response. Hand over the job id and resolve the link at the moment of the click.

Terminal window
curl "https://api.tupsara.com/v1/pdf?limit=20&offset=0" \
-H "Authorization: Bearer pdf_live_..."

Every item in the page carries its own freshly signed link, on the same clock as the rest. Listing is a way to find jobs, not a way to build a permanent table of URLs.