> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.recursiv.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.recursiv.io/_mcp/server.

# Storage

## Overview

Each project can have one or more storage buckets backed by Cloudflare R2. The SDK provides methods to manage buckets, list objects, and generate presigned URLs for secure uploads and downloads.

All storage methods are available on `r.storage`.

## Methods

| Method                    | Description                                         |
| ------------------------- | --------------------------------------------------- |
| `listBuckets(params)`     | List all buckets for a project.                     |
| `getBucket(name, params)` | Get a specific bucket by name.                      |
| `createBucket(input)`     | Create a new bucket.                                |
| `ensureBucket(input)`     | Ensure a bucket exists (create if not). Idempotent. |
| `listItems(params)`       | List objects in a bucket.                           |
| `createFolder(input)`     | Create a folder in a bucket.                        |
| `getUploadUrl(input)`     | Get a presigned URL for uploading an object.        |
| `getDownloadUrl(params)`  | Get a presigned URL for downloading an object.      |
| `deleteObject(params)`    | Delete an object or folder from a bucket.           |

## List buckets

```typescript
const { data: buckets } = await r.storage.listBuckets({
  project_id: 'proj_123',
});

for (const bucket of buckets) {
  console.log(`${bucket.name} — created ${bucket.created_at}`);
}
```

**Parameters:**

| Parameter    | Type     | Required | Description                      |
| ------------ | -------- | -------- | -------------------------------- |
| `project_id` | `string` | Yes      | The project to list buckets for. |

## Get a bucket

```typescript
const { data: bucket } = await r.storage.getBucket('uploads', {
  project_id: 'proj_123',
});

console.log(bucket.id);
console.log(bucket.name);
```

## Create a bucket

```typescript
const { data: bucket } = await r.storage.createBucket({
  project_id: 'proj_123',
  name: 'uploads',
});

console.log(bucket.id);
console.log(bucket.name);
```

**Input fields:**

| Field        | Type     | Required | Description                                     |
| ------------ | -------- | -------- | ----------------------------------------------- |
| `project_id` | `string` | Yes      | Project to create the bucket in.                |
| `name`       | `string` | Yes      | Bucket name. Must be unique within the project. |

## Ensure a bucket (idempotent)

Creates the bucket if it does not exist, or returns the existing one. Use this in deployment scripts.

```typescript
const { data: bucket } = await r.storage.ensureBucket({
  project_id: 'proj_123',
  name: 'uploads',
});

console.log(bucket.name); // 'uploads'
```

> **Note**
>
> `ensureBucket` is idempotent. Calling it multiple times with the same project and name will not create duplicates.

## List items

```typescript
const { data: items } = await r.storage.listItems({
  project_id: 'proj_123',
  bucket_name: 'uploads',
});

for (const item of items) {
  console.log(`${item.key} — ${item.size} bytes — ${item.last_modified}`);
}
```

**Parameters:**

| Parameter     | Type      | Required | Description                               |
| ------------- | --------- | -------- | ----------------------------------------- |
| `project_id`  | `string`  | Yes      | The project that owns the bucket.         |
| `bucket_name` | `string`  | Yes      | Bucket name.                              |
| `prefix`      | `string?` | No       | Filter by key prefix (e.g., `'images/'`). |
| `limit`       | `number?` | No       | Max results to return.                    |

**Returns:** `SingleResponse<StorageBucketItem[]>`

```typescript
interface StorageBucketItem {
  key: string;
  size: number;
  last_modified: string;
  etag?: string;
}
```

## Create a folder

```typescript
await r.storage.createFolder({
  project_id: 'proj_123',
  bucket_name: 'uploads',
  folder_path: 'images/avatars',
});
```

## Upload a file (presigned URL)

Uploads use a two-step process: first get a presigned URL from the API, then `PUT` the file directly to R2.

```typescript
// 1. Get a presigned upload URL
const { data: { url } } = await r.storage.getUploadUrl({
  project_id: 'proj_123',
  bucket_name: 'uploads',
  key: 'images/logo.png',
  content_type: 'image/png',
});

// 2. Upload the file directly to the presigned URL
const fileBuffer = await fs.promises.readFile('./logo.png');

await fetch(url, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/png' },
  body: fileBuffer,
});

console.log('Upload complete!');
```

**Input fields:**

| Field          | Type      | Required | Description                                           |
| -------------- | --------- | -------- | ----------------------------------------------------- |
| `project_id`   | `string`  | Yes      | The project that owns the bucket.                     |
| `bucket_name`  | `string`  | Yes      | Bucket name.                                          |
| `key`          | `string`  | Yes      | Object key (path within the bucket).                  |
| `content_type` | `string?` | No       | MIME type (e.g., `'image/png'`, `'application/pdf'`). |

## Download a file (presigned URL)

```typescript
// 1. Get a presigned download URL
const { data: { url } } = await r.storage.getDownloadUrl({
  project_id: 'proj_123',
  bucket_name: 'uploads',
  key: 'images/logo.png',
});

// 2. Download the file
const response = await fetch(url);
const buffer = await response.arrayBuffer();

// Or redirect the user to the URL in a web app
console.log('Download URL:', url);
```

## Delete an object

```typescript
await r.storage.deleteObject({
  project_id: 'proj_123',
  bucket_name: 'uploads',
  key: 'images/old-logo.png',
});
```

## Full example: upload and download flow

```typescript
import { Recursiv } from '@recursiv/sdk';
import fs from 'node:fs';

const r = new Recursiv();

const PROJECT_ID = 'proj_123';
const BUCKET = 'user-uploads';

// 1. Ensure the bucket exists
await r.storage.ensureBucket({
  project_id: PROJECT_ID,
  name: BUCKET,
});

// 2. Create a folder structure
await r.storage.createFolder({
  project_id: PROJECT_ID,
  bucket_name: BUCKET,
  folder_path: 'avatars',
});

// 3. Upload a file
const { data: upload } = await r.storage.getUploadUrl({
  project_id: PROJECT_ID,
  bucket_name: BUCKET,
  key: 'avatars/user_123.jpg',
  content_type: 'image/jpeg',
});

const file = await fs.promises.readFile('./avatar.jpg');
await fetch(upload.url, {
  method: 'PUT',
  headers: { 'Content-Type': 'image/jpeg' },
  body: file,
});

// 4. List uploaded files
const { data: items } = await r.storage.listItems({
  project_id: PROJECT_ID,
  bucket_name: BUCKET,
  prefix: 'avatars/',
});

console.log('Files in avatars/:', items.map((i) => i.key));

// 5. Get a download URL to share
const { data: download } = await r.storage.getDownloadUrl({
  project_id: PROJECT_ID,
  bucket_name: BUCKET,
  key: 'avatars/user_123.jpg',
});

console.log('Download URL:', download.url);

// 6. Clean up
await r.storage.deleteObject({
  project_id: PROJECT_ID,
  bucket_name: BUCKET,
  key: 'avatars/user_123.jpg',
});
```