Server client
new S3Delivery() and every method it exposes.
The S3Delivery class is the server-side client. It holds your secret API key,
so use it only on a server — in a route handler, a background job, or an AI
tool. Never ship it to the browser.
import { S3Delivery } from "s3delivery";
const s3 = new S3Delivery(); // reads S3DELIVERY_TOKENConstructor
new S3Delivery(options?: {
token?: string;
apiUrl?: string;
fetch?: typeof fetch;
});| Option | Default | Notes |
|---|---|---|
token | S3DELIVERY_TOKEN env var | Your secret API key (s3d_live_…). Throws MISSING_TOKEN if absent. |
apiUrl | S3DELIVERY_API_URL env var, else the bundled default | Override the API base URL. |
fetch | global fetch | Custom fetch implementation (for testing or non-standard runtimes). |
If no token is passed and S3DELIVERY_TOKEN is not set, the constructor throws
an S3DeliveryError with code MISSING_TOKEN.
Methods at a glance
| Method | Description |
|---|---|
upload | End-to-end upload: reserve → bytes straight to bucket → confirm. Returns the file. |
createUpload | Low-level: reserve a record and mint presigned upload URL(s). |
complete | Low-level: confirm an upload finished. |
list | List your files, newest first. |
get | Get a fresh signed URL plus metadata. |
download | Fetch the raw bytes as a Blob. |
exists | true / false — never throws on a missing or deleted file. |
update | Rename a file or change its visibility. |
delete | Delete the object and soft-delete the record. |
usage | Storage used, file count, plan name, and plan limits. |
const file = await s3.upload(blob, { name: "report.pdf", visibility: "private" });
const { url } = await s3.get(file.id);
const bytes = await s3.download(file.id); // Blob
if (await s3.exists(file.id)) await s3.delete(file.id);upload
The hero method. Reserves a record, sends the bytes directly to your bucket, and confirms — all in one call.
upload(
file: Blob & { name?: string },
opts?: {
name?: string;
contentType?: string;
visibility?: "private" | "public" | "unlisted";
// plus upload-engine options: onProgress, retries, retryDelay,
// maxRetryDelay, concurrency, signal
},
): Promise<S3DeliveryFile>;| Param | Default | Notes |
|---|---|---|
file | — | A Blob, File, or any Blob-like value. |
opts.name | file.name, else "file" | Filename to store. |
opts.contentType | file.type, else application/octet-stream | MIME type. |
opts.visibility | "private" | "private", "unlisted", or "public". |
opts.onProgress | — | ({ loaded, total, percent }) => void. |
It accepts the same retry, concurrency, and signal options as the
upload engine, so large server uploads get parallel
multipart and automatic retry for free.
const file = await s3.upload(blob, {
name: "report.pdf",
visibility: "private",
onProgress: (p) => console.log(`${p.percent}%`),
});
console.log(file.id, file.url);createUpload
Low-level. Reserves a file record and returns presigned upload URL(s) without
sending any bytes. Most code should call upload instead; use this
when you broker the browser handshake yourself (this is what
createRouteHandler calls internally).
createUpload(input: {
filename: string;
contentType?: string;
size: number;
visibility?: "private" | "public" | "unlisted";
}): Promise<{
file: S3DeliveryFile;
upload:
| { mode: "single"; url: string; method: "PUT" }
| { mode: "multipart"; uploadId: string; partSize: number; parts: { partNumber: number; url: string }[] };
}>;The returned upload descriptor is single for files under 100 MB and
multipart (with one URL per 16 MB part) at or above that. Pass the descriptor to
uploadToPresigned to send the bytes.
complete
Low-level. Confirms an upload finished and flips the record to ready. Pairs with
createUpload.
complete(
fileId: string,
body?: { uploadId?: string; parts?: { partNumber: number; etag: string }[] },
): Promise<S3DeliveryFile>;For a multipart upload, pass the uploadId from the descriptor and the collected
part ETags. For a single upload, call it with no body. The API verifies the object
exists in the bucket before marking the file ready — if the PUT never landed,
complete throws.
list
Lists your ready files, newest first.
list(opts?: { limit?: number; offset?: number }): Promise<{
files: S3DeliveryFile[];
limit: number;
offset: number;
}>;| Param | Notes |
|---|---|
limit | Max files to return. Server default 50, max 100. |
offset | Number of files to skip, for pagination. |
const { files } = await s3.list({ limit: 20, offset: 0 });get
Returns a freshly-signed, short-lived URL plus the file's metadata.
get(
fileId: string,
opts?: { download?: boolean },
): Promise<{ url: string; file: S3DeliveryFile }>;Set download: true to get an attachment URL (Content-Disposition: attachment)
instead of an inline one. The signed url expires after 15 minutes for private
files and 24 hours for public/unlisted files; the stable file.url (/f/:id)
re-signs on every request, so store that for long-lived links.
const { url, file } = await s3.get(fileId, { download: true });download
Signs a fresh URL and fetches the bytes directly from the bucket, returning a
Blob. The bytes never pass through the API.
download(fileId: string): Promise<Blob>;const blob = await s3.download(fileId);
const buffer = Buffer.from(await blob.arrayBuffer());Throws DOWNLOAD_FAILED if the signed URL cannot be fetched.
exists
Returns whether a file exists and is not deleted — without throwing on a 404.
exists(fileId: string): Promise<boolean>;if (await s3.exists(fileId)) {
// safe to link / serve
}Other errors (network, auth) still propagate.
update
Renames a file or changes its visibility.
update(
fileId: string,
patch: { filename?: string; visibility?: "private" | "public" | "unlisted" },
): Promise<S3DeliveryFile>;await s3.update(fileId, { visibility: "public" });See Access control for what each visibility level means.
delete
Removes the object from your bucket and soft-deletes the record. Irreversible.
delete(fileId: string): Promise<{ id: string; deleted: true }>;await s3.delete(fileId);usage
Reports storage used, file count, the plan name, and the plan's limits.
usage(): Promise<{
usedBytes: number;
fileCount: number;
plan: string;
limits: { maxFileBytes: number; maxTotalBytes: number; maxApiKeys: number };
}>;const { usedBytes, limits } = await s3.usage();
const remaining = limits.maxTotalBytes - usedBytes;See Quotas & plan limits for the current numbers.
The S3DeliveryFile shape
Every file-returning method resolves to this object:
interface S3DeliveryFile {
id: string;
name: string;
contentType: string;
size: number;
status: "pending" | "ready" | "deleted";
visibility: "private" | "public" | "unlisted";
/** Stable URL that redirects to a freshly-signed bucket URL. */
url: string;
createdAt: string; // ISO 8601
uploadedAt: string | null; // ISO 8601, set on completion
}