# Collections API Documentation

Complete API reference for collection management endpoints.

**Base Path**: `/api/collections`

---

## Overview

Collections allow you to organize your books into custom categories with:
- **Auto-assignment rules**: Automatically add books matching criteria
- **View settings**: Per-device display preferences
- **Shelf mappings**: Sync to device-specific shelves (Kobo, KOReader)

---

## Endpoints

### List All Collections

**Endpoint**: `GET /api/collections`  
**Authentication**: Required  
**Description**: Get all collections for the authenticated user

**Response** (200 OK):
```json
{
  "collections": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Science Fiction",
      "description": "My sci-fi collection",
      "color": "#FF5733",
      "icon": "🚀",
      "auto_assign_rules": [
        {
          "field": "genre",
          "operator": "equals",
          "value": "Science Fiction",
          "priority": 1,
          "enabled": true
        }
      ],
      "view_settings": {
        "kobo": {
          "view_mode": "grid",
          "sort_order": "name",
          "items_per_page": 24
        }
      },
      "created_at": "2026-02-01T10:00:00Z"
    }
  ],
  "total": 1
}
```

### Create Collection

**Endpoint**: `POST /api/collections`  
**Authentication**: Required  
**Description**: Create a new collection

**Request Body**:
```json
{
  "name": "To Read",
  "description": "Books I want to read soon",
  "color": "#00FF00",
  "icon": "📖",
  "auto_assign_rules": [
    {
      "field": "tags",
      "operator": "contains",
      "value": "to-read",
      "priority": 1,
      "enabled": true
    }
  ],
  "view_settings": {
    "kobo": {
      "view_mode": "grid"
    },
    "koreader": {
      "view_mode": "list"
    }
  }
}
```

**Fields**:
- `name` (required): Collection name (max 255 chars)
- `description` (optional): Collection description
- `color` (optional): Hex color code (e.g., "#FF5733")
- `icon` (optional): Emoji icon (e.g., "🚀", "📖")
- `auto_assign_rules` (optional): Array of rule objects
- `view_settings` (optional): Per-device display preferences

**Rule Object**:
- `field`: Field to match on (genre, author, series, language, publisher, copyright_year, tags)
- `operator`: Comparison operator (equals, not_equals, contains, not_contains, starts_with, ends_with, greater_than, less_than)
- `value`: Value to compare against
- `priority`: Rule priority (1 = highest)
- `enabled`: Whether rule is active

**Response** (201 Created): Collection object

### Get Collection Details

**Endpoint**: `GET /api/collections/{id}`  
**Authentication**: Required  
**Description**: Get single collection with all books

**Path Parameters**:
- `id`: Collection UUID

**Response** (200 OK):
```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Science Fiction",
  "description": "My sci-fi collection",
  "color": "#FF5733",
  "icon": "🚀",
  "books": [
    {
      "media_item_id": "660e8400-e29b-41d4-a716-446655440000",
      "title": "Foundation",
      "author": "Isaac Asimov",
      "cover_image_path": "/covers/foundation.jpg"
    }
  ],
  "auto_assign_rules": [],
  "view_settings": {},
  "created_at": "2026-02-01T10:00:00Z"
}
```

### Update Collection

**Endpoint**: `PUT /api/collections/{id}`  
**Authentication**: Required  
**Description**: Update collection details

**Path Parameters**:
- `id`: Collection UUID

**Request Body**: All fields are optional
```json
{
  "name": "Sci-Fi Favorites",
  "description": "Updated description",
  "color": "#BLUE",
  "icon": "🌟"
}
```

**Response** (200 OK): Updated collection object

### Delete Collection

**Endpoint**: `DELETE /api/collections/{id}`  
**Authentication**: Required  
**Description**: Delete a collection (books are NOT deleted)

**Path Parameters**:
- `id`: Collection UUID

**Response** (204 No Content)

---

## Collection Books

### Add Books to Collection

**Endpoint**: `POST /api/collections/{id}/books`  
**Authentication**: Required  
**Description**: Add one or more books to a collection

**Path Parameters**:
- `id`: Collection UUID

**Request Body**:
```json
{
  "book_ids": [
    "660e8400-e29b-41d4-a716-446655440000",
    "770e8400-e29b-41d4-a716-446655440000",
    "880e8400-e29b-41d4-a716-446655440000"
  ]
}
```

**Response** (204 No Content)

### Remove Book from Collection

**Endpoint**: `DELETE /api/collections/{id}/books/{bookId}`  
**Authentication**: Required  
**Description**: Remove a single book from a collection

**Path Parameters**:
- `id`: Collection UUID
- `bookId`: Media Item UUID

**Response** (204 No Content)

### Bulk Remove Books

**Endpoint**: `POST /api/collections/{id}/books/bulk-remove`  
**Authentication**: Required  
**Description**: Remove multiple books at once (efficient)

**Path Parameters**:
- `id`: Collection UUID

**Request Body**:
```json
{
  "book_ids": [
    "660e8400-e29b-41d4-a716-446655440000",
    "770e8400-e29b-41d4-a716-446655440000"
  ]
}
```

**Response** (200 OK):
```json
{
  "removed": 2,
  "total": 2
}
```

**Example**: Removing 2 books, both successfully removed

### Get Collection Books

**Endpoint**: `GET /api/collections/{id}/books`  
**Authentication**: Required  
**Description**: Get all books in a collection

**Path Parameters**:
- `id`: Collection UUID

**Query Parameters**:
- `limit` (optional): Number of books to return (default: 50)
- `offset` (optional): Number of books to skip (default: 0)

**Response** (200 OK):
```json
{
  "books": [
    {
      "media_item_id": "660e8400-e29b-41d4-a716-446655440000",
      "title": "Foundation",
      "author": "Isaac Asimov",
      "cover_image_path": "/covers/foundation.jpg",
      "added_at": "2026-02-01T10:00:00Z"
    }
  ],
  "total": 1
}
```

---

## Collection Rules

### Test Collection Rules

**Endpoint**: `POST /api/collections/test-rules`  
**Authentication**: Required  
**Description**: Test which books would match given rules (without saving)

**Request Body**:
```json
{
  "rules": [
    {
      "field": "genre",
      "operator": "equals",
      "value": "Science Fiction"
    },
    {
      "field": "author",
      "operator": "contains",
      "value": "Asimov"
    }
  ]
}
```

**Supported Fields**:
- `genre`: Book genre
- `author`: Book author
- `series`: Book series name
- `language`: Book language
- `publisher`: Publisher name
- `copyright_year`: Publication year (numeric comparison)
- `tags`: Book tags

**Supported Operators**:
- `equals`: Exact match
- `not_equals`: Not equal
- `contains`: Contains substring (case-insensitive)
- `not_contains`: Does not contain
- `starts_with`: Starts with (case-insensitive)
- `ends_with`: Ends with (case-insensitive)
- `greater_than`: Greater than (numeric)
- `less_than`: Less than (numeric)

**Response** (200 OK):
```json
{
  "matches": [
    {
      "media_item_id": "660e8400-e29b-41d4-a716-446655440000",
      "title": "Foundation",
      "author": "Isaac Asimov",
      "cover_image_path": "/covers/foundation.jpg",
      "match_reason": "Matched rule: genre equals Science Fiction"
    }
  ],
  "total": 42
}
```

**Use Case**: Test rules before creating collection to verify correct book matching

---

## Device Shelf Mappings

### Get Device Shelf Mappings

**Endpoint**: `GET /api/devices/{deviceId}/collections`  
**Authentication**: Required  
**Description**: Get all collection-to-shelf mappings for a device

**Path Parameters**:
- `deviceId`: Device UUID

**Response** (200 OK):
```json
{
  "mappings": [
    {
      "id": "990e8400-e29b-41d4-a716-446655440000",
      "collection_id": "550e8400-e29b-41d4-a716-446655440000",
      "collection_name": "Science Fiction",
      "device_shelf_name": "Sci-Fi",
      "sync_direction": "bidirectional",
      "created_at": "2026-02-01T10:00:00Z"
    }
  ]
}
```

### Create Shelf Mapping

**Endpoint**: `POST /api/devices/{deviceId}/collections`  
**Authentication**: Required  
**Description**: Map a collection to a device shelf

**Path Parameters**:
- `deviceId`: Device UUID

**Request Body**:
```json
{
  "collection_id": "550e8400-e29b-41d4-a716-446655440000",
  "device_shelf_name": "Sci-Fi",
  "sync_direction": "bidirectional"
}
```

**Sync Directions**:
- `bidirectional`: Sync both ways between Bookmann and device
- `book_to_device`: Bookmann → Device only
- `device_to_book`: Device → Bookmann only
- `none`: No sync (mapping only)

**Response** (201 Created): Mapping object

### Update Shelf Mapping

**Endpoint**: `PUT /api/devices/{deviceId}/collections/{collectionId}`  
**Authentication**: Required  
**Description**: Update existing shelf mapping

**Path Parameters**:
- `deviceId`: Device UUID
- `collectionId`: Collection UUID

**Request Body**:
```json
{
  "device_shelf_name": "Science Fiction",
  "sync_direction": "book_to_device"
}
```

**Response** (200 OK): Updated mapping object

### Delete Shelf Mapping

**Endpoint**: `DELETE /api/devices/{deviceId}/collections/{collectionId}`  
**Authentication**: Required  
**Description**: Remove shelf mapping

**Path Parameters**:
- `deviceId`: Device UUID
- `collectionId`: Collection UUID

**Response** (204 No Content)

---

## Error Responses

All endpoints may return these errors:

**400 Bad Request**:
```json
{
  "error": "invalid request: validation failed"
}
```

**401 Unauthorized**:
```json
{
  "error": "authentication required"
}
```

**404 Not Found**:
```json
{
  "error": "collection not found"
}
```

**500 Internal Server Error**:
```json
{
  "error": "internal server error"
}
```

---

## Rate Limiting

- **Authenticated**: 100 requests per minute
- **Unauthenticated**: 10 requests per minute

Headers included:
```
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1643723400
```

---

## Bruno Tests

Complete API tests available in `bruno/collections/`:

- `Get Collections.bru`
- `Create Collection.bru`
- `Get Collection.bru`
- `Update Collection.bru`
- `Delete Collection.bru`
- `Add Books to Collection.bru`
- `Remove Book from Collection.bru`
- `Get Book Collections.bru`
- `Test Collection Rules.bru`
- `Bulk Remove Books.bru`

Run tests:
```bash
bruno run bruno/collections/
```

---

**Last Updated**: 2026-02-01  
**Version**: 1.0
