# Server client (/docs/server-client)



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.

```ts
import { S3Delivery } from "s3delivery";

const s3 = new S3Delivery(); // reads S3DELIVERY_TOKEN
```

## Constructor [#constructor]

```ts
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`](/docs/errors) with code `MISSING_TOKEN`.

## Methods at a glance [#methods-at-a-glance]

| Method                          | Description                                                                        |
| ------------------------------- | ---------------------------------------------------------------------------------- |
| [`upload`](#upload)             | End-to-end upload: reserve → bytes straight to bucket → confirm. Returns the file. |
| [`createUpload`](#createupload) | Low-level: reserve a record and mint presigned upload URL(s).                      |
| [`complete`](#complete)         | Low-level: confirm an upload finished.                                             |
| [`list`](#list)                 | List your files, newest first.                                                     |
| [`get`](#get)                   | Get a fresh signed URL plus metadata.                                              |
| [`download`](#download)         | Fetch the raw bytes as a `Blob`.                                                   |
| [`exists`](#exists)             | `true` / `false` — never throws on a missing or deleted file.                      |
| [`update`](#update)             | Rename a file or change its visibility.                                            |
| [`delete`](#delete)             | Delete the object and soft-delete the record.                                      |
| [`usage`](#usage)               | Storage used, file count, plan name, and plan limits.                              |

```ts
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` [#upload]

The hero method. Reserves a record, sends the bytes directly to your bucket, and
confirms — all in one call.

```ts
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](/docs/upload-engine), so large server uploads get parallel
multipart and automatic retry for free.

```ts
const file = await s3.upload(blob, {
  name: "report.pdf",
  visibility: "private",
  onProgress: (p) => console.log(`${p.percent}%`),
});

console.log(file.id, file.url);
```

## `createUpload` [#createupload]

Low-level. Reserves a file record and returns presigned upload URL(s) without
sending any bytes. Most code should call [`upload`](#upload) instead; use this
when you broker the browser handshake yourself (this is what
[`createRouteHandler`](/docs/route-handler) calls internally).

```ts
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`](/docs/upload-engine) to send the bytes.

## `complete` [#complete]

Low-level. Confirms an upload finished and flips the record to `ready`. Pairs with
[`createUpload`](#createupload).

```ts
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` [#list]

Lists your `ready` files, newest first.

```ts
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.         |

```ts
const { files } = await s3.list({ limit: 20, offset: 0 });
```

## `get` [#get]

Returns a freshly-signed, short-lived URL plus the file's metadata.

```ts
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.

```ts
const { url, file } = await s3.get(fileId, { download: true });
```

## `download` [#download]

Signs a fresh URL and fetches the bytes directly from the bucket, returning a
`Blob`. The bytes never pass through the API.

```ts
download(fileId: string): Promise<Blob>;
```

```ts
const blob = await s3.download(fileId);
const buffer = Buffer.from(await blob.arrayBuffer());
```

Throws `DOWNLOAD_FAILED` if the signed URL cannot be fetched.

## `exists` [#exists]

Returns whether a file exists and is not deleted — without throwing on a `404`.

```ts
exists(fileId: string): Promise<boolean>;
```

```ts
if (await s3.exists(fileId)) {
  // safe to link / serve
}
```

Other errors (network, auth) still propagate.

## `update` [#update]

Renames a file or changes its visibility.

```ts
update(
  fileId: string,
  patch: { filename?: string; visibility?: "private" | "public" | "unlisted" },
): Promise<S3DeliveryFile>;
```

```ts
await s3.update(fileId, { visibility: "public" });
```

See [Access control](/docs/access-control) for what each visibility level means.

## `delete` [#delete]

Removes the object from your bucket and soft-deletes the record. Irreversible.

```ts
delete(fileId: string): Promise<{ id: string; deleted: true }>;
```

```ts
await s3.delete(fileId);
```

## `usage` [#usage]

Reports storage used, file count, the plan name, and the plan's limits.

```ts
usage(): Promise<{
  usedBytes: number;
  fileCount: number;
  plan: string;
  limits: { maxFileBytes: number; maxTotalBytes: number; maxApiKeys: number };
}>;
```

```ts
const { usedBytes, limits } = await s3.usage();
const remaining = limits.maxTotalBytes - usedBytes;
```

See [Quotas & plan limits](/docs/limits) for the current numbers.

## The `S3DeliveryFile` shape [#the-s3deliveryfile-shape]

Every file-returning method resolves to this object:

```ts
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
}
```
