TurboJet
Storage

Access stored files

List, signed download URLs, public URLs, and delete via /api/storage.

After upload, objects stay in a private bucket. Access is controlled in the API: only the owning user can list, sign, or delete their READY files.

List files

GET /api/storage/

Requires auth. Returns the current user’s files with status READY.

Signed download URL

GET /api/storage/signed?path=users/{userId}/….pdf

Uses getSignedUrl from @repo/storage (default expiry 15 minutes). Anyone who has the URL can fetch the object until it expires — keep TTLs short and only return URLs to authorized callers.

const res = await fetch(
	`/api/storage/signed?path=${encodeURIComponent(filePath)}`,
	{ credentials: "include" },
);
const { url } = await res.json();
window.open(url, "_blank");

You can also call getSignedUrl from server code in @repo/storage when building your own handlers:

import { getSignedUrl } from "@repo/storage";

const { url } = await getSignedUrl({
	path: filePath,
	expiresIn: 60, // seconds
});

Public URL

GET /api/storage/public?path=…

Requires S3_PUBLIC_URL to be set. Returns a stable public URL (getPublicUrl) instead of a time-limited signature. Use only when the object is intentionally public via r2.dev or a custom CDN domain.

Prefer signed URLs

For private user files, use /api/storage/signed. Leave S3_PUBLIC_URL unset unless you need public assets.

Delete

DELETE /api/storage/:id

Deletes the object in the bucket (deleteObject) and marks the Prisma row DELETED. Only the owner can delete.

Optional redirect proxy

If you want browser-facing paths like /files/... instead of handing signed URLs to the client, you can add a Next.js route that checks auth, calls getSignedUrl, and redirects. That pattern is not shipped in this repo — keep ownership checks in the handler and consider Cache-Control if the content is safe to cache briefly.

On this page