# Image Upload & Management API

Upload images and manage your image library. For generation, see [image-generation.md](image-generation.md).

All endpoints require Project Key authentication via `X-API-Key` header.

---

## Upload Image

```
POST /api/v1/images/upload
```

Upload an image file with optional alias and flow association.

**Content-Type:** `multipart/form-data`

**Form Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `file` | file | Yes | Image file (PNG, JPEG, WebP) |
| `alias` | string | No | Project-scoped alias (e.g., `@logo`) |
| `flowId` | string | No | Flow UUID to associate with |
| `flowAlias` | string | No | Flow-scoped alias (requires flowId) |
| `meta` | string | No | JSON string with custom metadata |

**File Constraints:**

| Constraint | Limit |
|------------|-------|
| Max file size | 5MB |
| Supported formats | PNG, JPEG, JPG, WebP |
| MIME types | `image/png`, `image/jpeg`, `image/webp` |

**Example Request (curl):**

```bash
curl -X POST http://localhost:3000/api/v1/images/upload \
  -H "X-API-Key: YOUR_PROJECT_KEY" \
  -F "file=@logo.png" \
  -F "alias=@brand-logo" \
  -F 'meta={"tags": ["logo", "brand"]}'
```

**Response:** `201 Created`

```json
{
  "success": true,
  "data": {
    "id": "7c4ccf47-41ce-4718-afbc-8c553b2c631a",
    "projectId": "57c7f7f4-47de-4d70-9ebd-3807a0b63746",
    "flowId": null,
    "storageKey": "default/my-project/img/7c4ccf47-41ce-4718-afbc-8c553b2c631a",
    "storageUrl": "https://cdn.banatie.app/default/my-project/img/7c4ccf47-41ce-4718-afbc-8c553b2c631a",
    "mimeType": "image/png",
    "fileSize": 45678,
    "width": 512,
    "height": 512,
    "source": "uploaded",
    "alias": "@brand-logo",
    "focalPoint": null,
    "meta": { "tags": ["logo", "brand"] },
    "createdAt": "2025-11-28T10:00:00.000Z"
  }
}
```

> **Note:** The `id` field equals the filename in storage (UUID). Original filename is preserved in object metadata.

### flowId Behavior

| Value | Behavior |
|-------|----------|
| Not provided | Auto-generate `pendingFlowId`, lazy flow creation |
| `null` | No flow association |
| `"uuid"` | Associate with specified flow |

### Upload with Flow

```bash
# Associate with existing flow
curl -X POST .../images/upload \
  -F "file=@reference.png" \
  -F "flowId=flow-123" \
  -F "flowAlias=@reference"
```

---

## List Images

```
GET /api/v1/images
```

List all images with filtering and pagination.

**Query Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `flowId` | string | - | Filter by flow UUID |
| `source` | string | - | Filter by source: `generated`, `uploaded` |
| `alias` | string | - | Filter by exact alias match |
| `limit` | number | `20` | Results per page (max: 100) |
| `offset` | number | `0` | Pagination offset |
| `includeDeleted` | boolean | `false` | Include soft-deleted records |

**Example:**

```
GET /api/v1/images?source=uploaded&limit=10
```

**Response:**

```json
{
  "success": true,
  "data": [
    {
      "id": "7c4ccf47-...",
      "storageUrl": "http://...",
      "source": "uploaded",
      "alias": "@brand-logo",
      "width": 512,
      "height": 512,
      "createdAt": "2025-11-28T10:00:00.000Z"
    }
  ],
  "pagination": {
    "limit": 10,
    "offset": 0,
    "total": 25,
    "hasMore": true
  }
}
```

---

## Get Image

```
GET /api/v1/images/:id_or_alias
```

Get a single image by UUID or alias.

**Path Parameter:**
- `id_or_alias` - Image UUID or `@alias`

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `flowId` | string | Flow context for alias resolution |

**Examples:**

```
# By UUID
GET /api/v1/images/7c4ccf47-41ce-4718-afbc-8c553b2c631a

# By project alias
GET /api/v1/images/@brand-logo

# By technical alias (requires flowId)
GET /api/v1/images/@last?flowId=flow-123

# By flow alias
GET /api/v1/images/@hero?flowId=flow-123
```

**Response:**

```json
{
  "success": true,
  "data": {
    "id": "7c4ccf47-41ce-4718-afbc-8c553b2c631a",
    "projectId": "57c7f7f4-47de-4d70-9ebd-3807a0b63746",
    "flowId": null,
    "storageKey": "default/my-project/img/7c4ccf47-41ce-4718-afbc-8c553b2c631a",
    "storageUrl": "https://cdn.banatie.app/default/my-project/img/7c4ccf47-41ce-4718-afbc-8c553b2c631a",
    "mimeType": "image/png",
    "fileSize": 45678,
    "width": 512,
    "height": 512,
    "source": "uploaded",
    "alias": "@brand-logo",
    "focalPoint": null,
    "fileHash": null,
    "generationId": null,
    "meta": { "tags": ["logo", "brand"] },
    "createdAt": "2025-11-28T10:00:00.000Z",
    "updatedAt": "2025-11-28T10:00:00.000Z",
    "deletedAt": null
  }
}
```

---

## Update Image Metadata

```
PUT /api/v1/images/:id_or_alias
```

Update image metadata (focal point, custom metadata).

**Request Body:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `focalPoint` | object | Focal point: `{ x: 0.0-1.0, y: 0.0-1.0 }` |
| `meta` | object | Custom metadata |

**Example:**

```json
// PUT /api/v1/images/@brand-logo
{
  "focalPoint": { "x": 0.5, "y": 0.3 },
  "meta": {
    "description": "Updated brand logo",
    "tags": ["logo", "brand", "2025"]
  }
}
```

**Response:** Updated image object.

> **Note:** Alias assignment has its own dedicated endpoint.

---

## Assign Alias

```
PUT /api/v1/images/:id_or_alias/alias
```

Assign or remove a project-scoped alias.

**Request Body:**

```json
// Assign alias
{ "alias": "@new-logo" }

// Remove alias
{ "alias": null }
```

**Override Behavior:**
- If another image has this alias, it loses the alias
- The new image gets the alias
- Old image is preserved, just unlinked

**Example:**

```bash
curl -X PUT http://localhost:3000/api/v1/images/7c4ccf47-.../alias \
  -H "X-API-Key: YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"alias": "@primary-logo"}'
```

---

## Delete Image

```
DELETE /api/v1/images/:id_or_alias
```

Permanently delete an image and its storage file.

**Behavior:**
- **Hard delete** - image record permanently removed
- Storage file deleted from MinIO
- Cascading updates:
  - Related generations: `outputImageId` set to null
  - Flow aliases: image removed from flow's aliases
  - Referenced images: removed from generation's referencedImages

**Response:** `200 OK`

```json
{
  "success": true,
  "message": "Image deleted"
}
```

> **Warning:** This cannot be undone. The image file is permanently removed.

---

## Image Response Fields

| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Image UUID (same as filename in storage) |
| `projectId` | string | Project UUID |
| `flowId` | string | Associated flow UUID (null if none) |
| `storageKey` | string | Storage path: `{org}/{project}/img/{imageId}` |
| `storageUrl` | string | CDN URL: `https://cdn.banatie.app/{org}/{project}/img/{imageId}` |
| `mimeType` | string | Image MIME type |
| `fileSize` | number | File size in bytes |
| `width` | number | Image width in pixels |
| `height` | number | Image height in pixels |
| `source` | string | `"generated"` or `"uploaded"` |
| `alias` | string | Project-scoped alias (null if none) |
| `focalPoint` | object | `{ x, y }` coordinates (0.0-1.0) |
| `fileHash` | string | SHA-256 hash for deduplication |
| `generationId` | string | Source generation UUID (if generated) |
| `meta` | object | Custom metadata |
| `createdAt` | string | ISO timestamp |
| `updatedAt` | string | ISO timestamp |
| `deletedAt` | string | Soft delete timestamp (null if active) |

### Accessing Images

Use `storageUrl` for direct CDN access:

```html
<!-- Direct access via UUID -->
<img src="https://cdn.banatie.app/default/my-project/img/7c4ccf47-41ce-4718-afbc-8c553b2c631a" />

<!-- Access via alias (goes through API) -->
<img src="https://cdn.banatie.app/default/my-project/img/@brand-logo" />
```

> **Note:** UUID URLs are served directly from MinIO (fast, cacheable). Alias URLs require API resolution.

---

## Storage Organization

Images are stored in MinIO with a simplified path structure:

```
bucket/
  {orgSlug}/
    {projectSlug}/
      img/
        {imageId}     # UUID, no file extension
        {imageId}     # Content-Type in object metadata
        ...
```

**Key points:**
- **Filename = Image ID (UUID)** - No file extensions
- **Content-Type** stored in MinIO object metadata
- **Original filename** preserved in metadata for reference
- **Single `img/` directory** for all images (generated + uploaded)

---

## Error Codes

| HTTP Status | Code | Description |
|-------------|------|-------------|
| 400 | `VALIDATION_ERROR` | Invalid parameters |
| 400 | `FILE_TOO_LARGE` | File exceeds 5MB limit |
| 400 | `UNSUPPORTED_FILE_TYPE` | Not PNG, JPEG, or WebP |
| 400 | `ALIAS_FORMAT_CHECK` | Alias must start with @ |
| 401 | `UNAUTHORIZED` | Missing or invalid API key |
| 404 | `IMAGE_NOT_FOUND` | Image or alias doesn't exist |
| 404 | `ALIAS_NOT_FOUND` | Alias doesn't resolve to any image |

---

## See Also

- [Basic Generation](image-generation.md) - Generate images
- [Advanced Generation](image-generation-advanced.md) - References, aliases, flows
- [Live URLs](live-url.md) - CDN and public access
