# Storage

`GET | POST | PATCH | DELETE /v1/uploads` — Your reference bucket: uploaded files, private folders, one shared quota.

- Base URL: https://eroq.ai/v1
- Auth: `Authorization: Bearer eroq_sk_…` (create keys at https://eroq.ai/dashboard/keys)
- Credits: Free · counts against the shared bucket quota

The workspace's **reference bank** — the files behind /studio/storage. They live in the eroq Store bucket under `workspaces/{workspace_id}/uploads/` (single-level folders are prefixes of the key), share the quota with the library (5 GB free, more on every plan), cost **no credits**, and are NEVER public: every read is a short-lived signed URL, so nothing can hotlink your references off-site. Character and element reference photos live in the same bucket under `workspaces/{ws}/references/{characters|elements}/{id}/` and count against the same quota (`uploadsBytes` in the meter).

There is no metadata database — the key IS the record: `workspaces/{ws}/uploads/{folder?}/{id}-{Name}.ext`. The id prefix survives renames and moves, so a reference keeps working even while you reorganize. File names double as @-mention handles (`@Red Jacket` in a prompt, matched case-insensitively). Root folders are capped at 20 per workspace (409 `folder_limit`), and folder deletion is a single native recursive directory delete — children AND the directory object itself go.

The bucket takes **images and videos only** — a reference is a face, a place, a prop or a clip, so anything else answers `415 type_not_accepted`. Access follows the workspace role ladder: `storage:read` to browse, `storage:upload` (Creator and above by default) to add or organise, `storage:delete` (Developer and above) to remove; a key acts with its owner's role.

`POST /v1/uploads` takes multipart `file` plus optional `folder` and `name`; over the bucket ceiling it answers 409 `storage_full` (free space or upgrade — generations still work, they just stop auto-saving). `PATCH /v1/uploads/{id}` renames and/or moves. `PATCH /v1/uploads/folders/{name}` and `DELETE /v1/uploads/folders/{name}` rezone or remove a whole folder. Reference an upload in any generation's `elements` array as `upload:<id>` — its photo joins the numbered `references` slots.

GET /v1/uploads/usage returns just the meter (`{ usage: { bucketBytes, limitBytes, over } }`) — cheap enough to consult before every render.

## Operations

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/uploads` | List every reference (files + folders + the shared-bucket usage). |
| `POST` | `/v1/uploads` | Upload a reference file (multipart `file`, optional `folder`, `name`). A new folder beyond the 20-folder cap answers 409 `folder_limit`. |
| `PATCH` | `/v1/uploads/{id}` | Rename and/or move a file. |
| `DELETE` | `/v1/uploads/{id}` | Remove a file from the bucket. |
| `PATCH` | `/v1/uploads/folders/{name}` | Rename a folder — every child is rezoned (each keeps its mention-stable id), then the emptied old directory is deleted. |
| `DELETE` | `/v1/uploads/folders/{name}` | Delete a folder and everything in it — one native recursive delete, the directory object included. |

## GET /v1/uploads

List every reference (files + folders + the shared-bucket usage).

### Example request

```bash
curl https://eroq.ai/v1/uploads \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/uploads', {
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.get("https://eroq.ai/v1/uploads", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://eroq.ai/v1/uploads", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "files": [{
    "id": "0b52a91f-3c", "name": "Red Jacket", "folder": "Wardrobe",
    "file": "0b52a91f-3c-Red_Jacket.webp", "ext": "webp",
    "bytes": 184320, "lastModified": "2026-09-21T10:11:12.000",
    "key": "workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp",
    "url": "https://store.eroq.ai/workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp?token=…"
  }],
  "folders": ["Wardrobe"],
  "folderLimit": 20,
  "usage": { "totalBytes": 1480000000, "publishedBytes": 0, "libraryBytes": 220000000,
    "uploadsBytes": 640000, "bucketBytes": 220640000, "limitBytes": 5368709120, "over": false }
}
```

## POST /v1/uploads

Upload a reference file (multipart `file`, optional `folder`, `name`). A new folder beyond the 20-folder cap answers 409 `folder_limit`.

### Example request

```bash
curl https://eroq.ai/v1/uploads \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "file": "@photo.webp",
  "folder": "Characters",
  "name": "Red Jacket"
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/uploads', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "file": "@photo.webp",
    "folder": "Characters",
    "name": "Red Jacket"
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/uploads",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "file": "@photo.webp",
        "folder": "Characters",
        "name": "Red Jacket"
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "file": "@photo.webp",
  "folder": "Characters",
  "name": "Red Jacket"
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/uploads", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "files": [{
    "id": "0b52a91f-3c", "name": "Red Jacket", "folder": "Wardrobe",
    "file": "0b52a91f-3c-Red_Jacket.webp", "ext": "webp",
    "bytes": 184320, "lastModified": "2026-09-21T10:11:12.000",
    "key": "workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp",
    "url": "https://store.eroq.ai/workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp?token=…"
  }],
  "folders": ["Wardrobe"],
  "folderLimit": 20,
  "usage": { "totalBytes": 1480000000, "publishedBytes": 0, "libraryBytes": 220000000,
    "uploadsBytes": 640000, "bucketBytes": 220640000, "limitBytes": 5368709120, "over": false }
}
```

## PATCH /v1/uploads/{id}

Rename and/or move a file.

### Example request

```bash
curl -X PATCH https://eroq.ai/v1/uploads/UPLOAD_ID \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Leather Jacket",
  "folder": "Wardrobe"
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/uploads/UPLOAD_ID', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Leather Jacket",
    "folder": "Wardrobe"
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.patch(
    "https://eroq.ai/v1/uploads/UPLOAD_ID",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "name": "Leather Jacket",
        "folder": "Wardrobe"
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "name": "Leather Jacket",
  "folder": "Wardrobe"
}`)

	req, _ := http.NewRequest("PATCH", "https://eroq.ai/v1/uploads/UPLOAD_ID", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "files": [{
    "id": "0b52a91f-3c", "name": "Red Jacket", "folder": "Wardrobe",
    "file": "0b52a91f-3c-Red_Jacket.webp", "ext": "webp",
    "bytes": 184320, "lastModified": "2026-09-21T10:11:12.000",
    "key": "workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp",
    "url": "https://store.eroq.ai/workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp?token=…"
  }],
  "folders": ["Wardrobe"],
  "folderLimit": 20,
  "usage": { "totalBytes": 1480000000, "publishedBytes": 0, "libraryBytes": 220000000,
    "uploadsBytes": 640000, "bucketBytes": 220640000, "limitBytes": 5368709120, "over": false }
}
```

## DELETE /v1/uploads/{id}

Remove a file from the bucket.

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/uploads/UPLOAD_ID \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/uploads/UPLOAD_ID', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/uploads/UPLOAD_ID", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/uploads/UPLOAD_ID", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "files": [{
    "id": "0b52a91f-3c", "name": "Red Jacket", "folder": "Wardrobe",
    "file": "0b52a91f-3c-Red_Jacket.webp", "ext": "webp",
    "bytes": 184320, "lastModified": "2026-09-21T10:11:12.000",
    "key": "workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp",
    "url": "https://store.eroq.ai/workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp?token=…"
  }],
  "folders": ["Wardrobe"],
  "folderLimit": 20,
  "usage": { "totalBytes": 1480000000, "publishedBytes": 0, "libraryBytes": 220000000,
    "uploadsBytes": 640000, "bucketBytes": 220640000, "limitBytes": 5368709120, "over": false }
}
```

## PATCH /v1/uploads/folders/{name}

Rename a folder — every child is rezoned (each keeps its mention-stable id), then the emptied old directory is deleted.

### Example request

```bash
curl -X PATCH https://eroq.ai/v1/uploads/folders/{name} \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Wardrobe"
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/uploads/folders/{name}', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "name": "Wardrobe"
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.patch(
    "https://eroq.ai/v1/uploads/folders/{name}",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "name": "Wardrobe"
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "name": "Wardrobe"
}`)

	req, _ := http.NewRequest("PATCH", "https://eroq.ai/v1/uploads/folders/{name}", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "files": [{
    "id": "0b52a91f-3c", "name": "Red Jacket", "folder": "Wardrobe",
    "file": "0b52a91f-3c-Red_Jacket.webp", "ext": "webp",
    "bytes": 184320, "lastModified": "2026-09-21T10:11:12.000",
    "key": "workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp",
    "url": "https://store.eroq.ai/workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp?token=…"
  }],
  "folders": ["Wardrobe"],
  "folderLimit": 20,
  "usage": { "totalBytes": 1480000000, "publishedBytes": 0, "libraryBytes": 220000000,
    "uploadsBytes": 640000, "bucketBytes": 220640000, "limitBytes": 5368709120, "over": false }
}
```

## DELETE /v1/uploads/folders/{name}

Delete a folder and everything in it — one native recursive delete, the directory object included.

### Example request

```bash
curl -X DELETE https://eroq.ai/v1/uploads/folders/{name} \
  -H "Authorization: Bearer $EROQ_API_KEY"
```

```javascript
const res = await fetch('https://eroq.ai/v1/uploads/folders/{name}', {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${process.env.EROQ_API_KEY}` },
})
console.log(await res.json())
```

```python
import os, requests

res = requests.delete("https://eroq.ai/v1/uploads/folders/{name}", headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"})
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://eroq.ai/v1/uploads/folders/{name}", nil)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

### Response


```json
{
  "files": [{
    "id": "0b52a91f-3c", "name": "Red Jacket", "folder": "Wardrobe",
    "file": "0b52a91f-3c-Red_Jacket.webp", "ext": "webp",
    "bytes": 184320, "lastModified": "2026-09-21T10:11:12.000",
    "key": "workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp",
    "url": "https://store.eroq.ai/workspaces/1f0c…/uploads/Wardrobe/0b52a91f-3c-Red_Jacket.webp?token=…"
  }],
  "folders": ["Wardrobe"],
  "folderLimit": 20,
  "usage": { "totalBytes": 1480000000, "publishedBytes": 0, "libraryBytes": 220000000,
    "uploadsBytes": 640000, "bucketBytes": 220640000, "limitBytes": 5368709120, "over": false }
}
```

---
Canonical: https://eroq.ai/docs/uploads · Index for agents: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json
