Files
bookhoard/docs/API_REFERENCE.md
T
john-okeefe 33f26f3d7d docs: add implementation plan and update documentation
- Add comprehensive implementation plan for universal sync system
- Update README with API reference and device setup guides
- Add KOBO_SETUP.md device configuration guide
2026-01-31 18:25:27 -05:00

1309 lines
23 KiB
Markdown

# Bookmann API Reference
Complete API documentation for Bookmann v1.0 with Universal Cross-Platform Sync support.
## Table of Contents
1. [Authentication](#authentication)
2. [Users & Profiles](#users--profiles)
3. [Libraries](#libraries)
4. [Media Items](#media-items)
5. [Reading Progress](#reading-progress)
6. [Notes & Highlights](#notes--highlights)
7. [Ratings](#ratings)
8. [Device Management](#device-management)
9. [Sync Protocol - KOReader](#sync-protocol---koreader)
10. [Sync Protocol - Kobo](#sync-protocol---kobo)
11. [Universal Progress](#universal-progress)
12. [Conflicts](#conflicts)
13. [Sync Queue](#sync-queue)
14. [WebSocket](#websocket)
## Base URL
```
Production: https://your-domain.com/api
Development: http://localhost:8765/api
```
## Authentication
Most endpoints require authentication. Include your JWT token in the Authorization header:
```
Authorization: Bearer <your-jwt-token>
```
### Register User
```http
POST /api/auth/register
Content-Type: application/json
{
"email": "user@example.com",
"username": "john",
"password": "SecureP@ss123!",
"first_name": "John",
"last_name": "Doe"
}
```
**Response** (201):
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "d4f5g6h7...",
"user": {
"id": "uuid-here",
"email": "user@example.com",
"username": "john",
"role": "user",
"theme": "tokyo-night",
"created_at": "2026-01-31T10:00:00Z"
}
}
```
### Login
```http
POST /api/auth/login
Content-Type: application/json
{
"email": "user@example.com",
"password": "SecureP@ss123!"
}
```
**Response** (200):
```json
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "d4f5g6h7...",
"user": {
"id": "uuid-here",
"email": "user@example.com",
"username": "john",
"role": "user"
}
}
```
### Refresh Token
```http
POST /api/auth/refresh
Content-Type: application/json
{
"refresh_token": "d4f5g6h7..."
}
```
**Response** (200):
```json
{
"token": "new-jwt-token",
"refresh_token": "new-refresh-token"
}
```
### Logout
```http
POST /api/auth/logout
Authorization: Bearer <token>
```
**Response** (204): No Content
## Users & Profiles
### Get Current User
```http
GET /api/users/me
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"id": "uuid",
"email": "user@example.com",
"username": "john",
"first_name": "John",
"last_name": "Doe",
"theme": "tokyo-night",
"role": "user",
"max_devices": 10,
"created_at": "2026-01-31T10:00:00Z"
}
```
### Update Profile
```http
PUT /api/users/me/profile
Authorization: Bearer <token>
Content-Type: application/json
{
"first_name": "John",
"last_name": "Smith"
}
```
### Update Theme
```http
PUT /api/users/me/theme
Authorization: Bearer <token>
Content-Type: application/json
{
"theme": "dracula"
}
```
### Change Password
```http
PUT /api/users/me/password
Authorization: Bearer <token>
Content-Type: application/json
{
"current_password": "oldPassword",
"new_password": "NewSecureP@ss123!"
}
```
### Update Scan Settings
```http
PUT /api/library/scan-settings
Authorization: Bearer <token>
Content-Type: application/json
{
"scan_frequency_minutes": 60,
"auto_scan_enabled": true
}
```
## Libraries
### Get Visible Libraries
```http
GET /api/libraries/visible
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"libraries": [
{
"id": "uuid",
"name": "My Ebooks",
"description": "Ebook collection",
"type_name": "ebooks",
"is_visible": true
}
]
}
```
### Get Library Details
```http
GET /api/libraries/{library_id}
Authorization: Bearer <token>
```
### Create Library (Admin Only)
```http
POST /api/libraries
Authorization: Bearer <token>
Content-Type: application/json
{
"name": "Comics Collection",
"description": "Digital comics",
"type": "comics"
}
```
### Add Library Folder (Admin Only)
```http
POST /api/libraries/{library_id}/folders
Authorization: Bearer <token>
Content-Type: application/json
{
"folder_path": "/path/to/comics"
}
```
### Set Library Visibility (Admin Only)
```http
POST /api/libraries/visibility
Authorization: Bearer <token>
Content-Type: application/json
{
"user_id": "user-uuid",
"library_id": "library-uuid",
"is_visible": true
}
```
## Media Items
### List Media Items
```http
GET /api/media-items?library_id={library_id}&limit=20&offset=0
Authorization: Bearer <token>
```
**Query Parameters**:
- `library_id` (required): UUID of library
- `limit`: Number of items to return (max 100, default 20)
- `offset`: Number of items to skip
**Response** (200):
```json
{
"media_items": [
{
"id": "uuid",
"library_id": "uuid",
"title": "Book Title",
"author": "Author Name",
"description": "Book description",
"file_path": "/path/to/book.epub",
"file_size": 1024000,
"mime_type": "application/epub+zip",
"cover_image_path": "/path/to/cover.jpg",
"series": "Series Name",
"series_number": 1,
"tags": "sci-fi, space opera",
"language": "en",
"page_count": 350,
"genre": "Science Fiction",
"copyright_year": 2023,
"created_at": "2026-01-31T10:00:00Z"
}
],
"total": 100
}
```
### Get Media Item
```http
GET /api/media-items/{media_id}
Authorization: Bearer <token>
```
### Search Media Items
```http
GET /api/media-items/search?q={query}&limit=20&offset=0
Authorization: Bearer <token>
```
**Query Parameters**:
- `q` (required): Search query (minimum 2 characters)
- `limit`: Number of results (default 20)
- `offset`: Number to skip
**Response** (200):
```json
{
"results": [
{
"id": "uuid",
"title": "Book Title",
"author": "Author Name",
"match_score": 0.95
}
]
}
```
### Filter & Sort Media Items
```http
GET /api/media-items/filter
Authorization: Bearer <token>
Content-Type: application/json
{
"library_id": "uuid",
"author_filter": "Rowling",
"series_filter": "Harry Potter",
"genre_filter": "Fantasy",
"year_min": 1997,
"year_max": 2007,
"has_cover": true,
"sort": "title ASC",
"limit": 20,
"offset": 0
}
```
### Update Media Item (Admin Only)
```http
PUT /api/media-items/{media_id}
Authorization: Bearer <token>
Content-Type: application/json
{
"title": "Updated Title",
"author": "Updated Author",
"description": "Updated description",
"series": "Series",
"series_number": 2
}
```
### Delete Media Item (Admin Only)
```http
DELETE /api/media-items/{media_id}
Authorization: Bearer <token>
```
## Reading Progress
### Get Reading Progress
```http
GET /api/media-items/{media_id}/progress
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"media_item_id": "uuid",
"user_id": "uuid",
"current_page": 45,
"total_pages": 200,
"percentage": 0.225,
"character_offset": 15432,
"epubcfi": "epubcfi(/6/4/2:15)",
"chapter": 3,
"chapter_progress": 0.5,
"last_read_at": "2026-01-31T10:00:00Z",
"format_group": "reflowable",
"viewport_y": 0.12,
"zoom_level": 1.0
}
```
### Update Reading Progress
```http
PUT /api/media-items/{media_id}/progress
Authorization: Bearer <token>
Content-Type: application/json
{
"source": "web",
"location": {
"percentage": 0.45678,
"epubcfi": "epubcfi(/6/4/2:15)",
"character": 15432,
"chapter": 3,
"page": 89,
"total_pages": 200
},
"device_metadata": {
"device_type": "web",
"user_agent": "Mozilla/5.0..."
}
}
```
**Response** (200):
```json
{
"sync_status": "success",
"progress_updated": true,
"devices_notified": ["device-1", "device-2"],
"broadcast": true
}
```
### Delete Reading Progress
```http
DELETE /api/media-items/{media_id}/progress
Authorization: Bearer <token>
```
## Notes & Highlights
### Get Notes
```http
GET /api/media-items/{media_id}/notes
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"notes": [
{
"id": "uuid",
"media_item_id": "uuid",
"user_id": "uuid",
"content": "This is an interesting passage...",
"position": "epubcfi(/6/4/2:15)",
"percentage_location": 0.45,
"character_start": 15432,
"character_end": 15480,
"epubcfi_location": "epubcfi(/6/4/2:15)",
"created_at": "2026-01-31T10:00:00Z",
"updated_at": "2026-01-31T10:00:00Z"
}
]
}
```
### Create Note
```http
POST /api/media-items/{media_id}/notes
Authorization: Bearer <token>
Content-Type: application/json
{
"content": "This is a note",
"position": "epubcfi(/6/4/2:15)",
"percentage_location": 0.45,
"epubcfi_location": "epubcfi(/6/4/2:15)"
}
```
### Update Note
```http
PUT /api/media-items/notes/{note_id}
Authorization: Bearer <token>
Content-Type: application/json
{
"content": "Updated note content",
"position": "epubcfi(/6/4/2:20)"
}
```
### Delete Note
```http
DELETE /api/media-items/notes/{note_id}
Authorization: Bearer <token>
```
### Get Highlights
```http
GET /api/media-items/{media_id}/highlights
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"highlights": [
{
"id": "uuid",
"media_item_id": "uuid",
"user_id": "uuid",
"selection_text": "Highlighted text passage...",
"start_position": "epubcfi(/6/4/2:15)",
"end_position": "epubcfi(/6/4/2:20)",
"color": "#ffff00",
"percentage_start": 0.45,
"percentage_end": 0.47,
"character_start": 15432,
"character_end": 15480,
"epubcfi_start": "epubcfi(/6/4/2:15)",
"epubcfi_end": "epubcfi(/6/4/2:20)",
"created_at": "2026-01-31T10:00:00Z"
}
]
}
```
### Create Highlight
```http
POST /api/media-items/{media_id}/highlights
Authorization: Bearer <token>
Content-Type: application/json
{
"selection_text": "Highlighted text...",
"start_position": "epubcfi(/6/4/2:15)",
"end_position": "epubcfi(/6/4/2:20)",
"color": "#ffff00",
"percentage_start": 0.45,
"percentage_end": 0.47
}
```
### Update Highlight
```http
PUT /api/media-items/highlights/{highlight_id}
Authorization: Bearer <token>
Content-Type: application/json
{
"selection_text": "Updated text",
"color": "#00ff00"
}
```
### Delete Highlight
```http
DELETE /api/media-items/highlights/{highlight_id}
Authorization: Bearer <token>
```
## Ratings
### Get Rating
```http
GET /api/media-items/{media_id}/rating
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"rating": 8,
"user_id": "uuid",
"media_item_id": "uuid"
}
```
### Set Rating
```http
POST /api/media-items/{media_id}/rating
Authorization: Bearer <token>
Content-Type: application/json
{
"rating": 8
}
```
**Rating Scale**: 1-10 (odd numbers = half-stars: 1=0.5★, 2=1★, 3=1.5★, ..., 9=4.5★, 10=5★)
### Update Rating
```http
PUT /api/media-items/{media_id}/rating
Authorization: Bearer <token>
Content-Type: application/json
{
"rating": 9
}
```
### Delete Rating
```http
DELETE /api/media-items/{media_id}/rating
Authorization: Bearer <token>
```
## Device Management
### Register Device
```http
POST /api/devices/register
Content-Type: application/json
{
"device_name": "My Kobo Clara",
"device_type": "kobo|koreader|web|mobile",
"device_identifier": "hardware-specific-id"
}
```
**Response** (201):
```json
{
"device_id": "uuid",
"registration_id": "registration-uuid",
"auth_url": "https://bookmann.com/devices/auth/confirm/abc123",
"qr_code": "data:image/png;base64,iVBORw0KG...",
"expires_in": 300
}
```
### Check Registration Status
```http
POST /api/devices/auth/status
Content-Type: application/json
{
"registration_id": "registration-uuid"
}
```
**Response** (200):
```json
{
"status": "pending|approved|expired",
"auth_token": "device-bearer-token...",
"device_id": "uuid",
"sync_endpoints": {
"progress": "https://bookmann.com/api/sync/progress",
"metadata": "https://bookmann.com/api/sync/metadata",
"annotations": "https://bookmann.com/api/sync/annotations"
}
}
```
### List User Devices
```http
GET /api/devices
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"devices": [
{
"id": "uuid",
"device_name": "My Kobo Clara",
"device_type": "kobo",
"last_sync": "2026-01-31T10:00:00Z",
"last_seen": "2026-01-31T10:05:00Z",
"sync_enabled": true,
"auto_sync": true,
"sync_frequency_minutes": 5
}
]
}
```
### Update Device Settings
```http
PUT /api/devices/{device_id}
Authorization: Bearer <token>
Content-Type: application/json
{
"device_name": "Updated Name",
"sync_enabled": true,
"auto_sync": true,
"sync_frequency_minutes": 5
}
```
### Revoke Device
```http
DELETE /api/devices/{device_id}
Authorization: Bearer <token>
```
## Sync Protocol - KOReader
### KOReader Progress Sync
```http
POST /api/sync/koreader/progress
Authorization: Bearer <device_token>
Content-Type: application/json
{
"library_id": "optional-uuid",
"books": [
{
"uuid": "book-uuid",
"title": "Book Title",
"authors": ["Author Name"],
"progress": 0.45,
"percentage": 0.45,
"last_read": "2026-01-30T20:00:00Z",
"chapter": 3,
"epubcfi": "epubcfi(/6/4/2:15)",
"character": 15432,
"bookmarks": [
{
"chapter": 3,
"datetime": "2026-01-30T19:55:00Z",
"notes": "highlighted text",
"pos0": "epubcfi(/6/4/2:15)",
"pos1": "epubcfi(/6/4/2:20)",
"page": 45,
"text": "highlighted text excerpt",
"type": "highlight"
}
],
"highlights": [],
"notes": []
}
]
}
```
**Response** (202):
```json
{
"sync_status": "accepted",
"books_synced": 1,
"conflicts": [
{
"book_uuid": "book-uuid",
"conflict_type": "progress_mismatch",
"device_progress": 0.45,
"server_progress": 0.42,
"resolution": "device_wins"
}
]
}
```
### KOReader Metadata Fetch
```http
GET /api/sync/koreader/metadata/{book_uuid}
Authorization: Bearer <device_token>
```
**Response** (200):
```json
{
"uuid": "book-uuid",
"title": "Book Title",
"authors": ["Author Name"],
"progress": {
"percentage": 0.42,
"character": 15432,
"epubcfi": "epubcfi(/6/4/2:15)",
"chapter": 3,
"chapter_progress": 0.234
},
"annotations": {
"highlights": [...],
"notes": [...],
"bookmarks": [...]
},
"last_sync": "2026-01-30T20:00:00Z"
}
```
## Sync Protocol - Kobo
### Kobo Markup Sync
```http
POST /api/sync/kobo/markup
Authorization: Bearer <device_token>
x-kobo-device: {"DeviceId":"device-id","Model":"Kobo Clara"}
Content-Type: application/json
{
"ReadingSync": [
{
"ContentId": "book-uuid",
"PercentRead": 45.6,
"EntitlementId": "entitlement-id",
"RemainingTimeMinutes": 120,
"LastModified": "2026-01-30T20:00:00Z"
}
],
"BookmarkSync": [
{
"ContentId": "book-uuid",
"BookmarkText": "highlighted text",
"BookmarkType": "annotation",
"BookmarkTitle": "Chapter 3"
}
]
}
```
**Response** (200):
```json
{
"Status": "Success",
"MarkupsSynced": 5,
"BookmarksSynced": 3
}
```
### Kobo Library Fetch
```http
GET /api/sync/kobo/library
Authorization: Bearer <device_token>
```
**Response** (200):
```json
{
"library_sync": [
{
"ContentId": "book-uuid",
"ContentType": "6",
"Title": "Book Title",
"Author": "Author Name",
"PercentRead": 42.3,
"PagesRemaining": 115,
"BookmarkCount": 3,
"LastModified": "2026-01-30T20:00:00Z"
}
]
}
```
## Universal Progress
### Get Universal Progress
```http
GET /api/progress/{book_uuid}
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"book_id": "book-uuid",
"format_group": "reflowable",
"universal_progress": 0.45678,
"location_references": {
"percentage": 0.45678,
"epubcfi": "epubcfi(/6/4/2:15)",
"character": 15432,
"chapter": 3,
"chapter_progress": 0.234,
"viewport_y": 0.12
},
"device_progress": {
"koreader": {
"percentage": 0.45678,
"last_sync": "2026-01-30T20:00:00Z"
},
"kobo": {
"percentage": 45.6,
"last_sync": "2026-01-30T19:55:00Z"
},
"web": {
"display_page": 89,
"total_pages": 200,
"last_sync": "2026-01-30T20:05:00Z"
}
},
"annotations": {
"highlights": [...],
"notes": [...],
"bookmarks": [...]
},
"conflicts": [
{
"id": "conflict-uuid",
"type": "progress",
"resolved": false,
"sources": ["koreader", "kobo"]
}
]
}
```
### Update Universal Progress
```http
POST /api/progress/{book_uuid}
Authorization: Bearer <token>
Content-Type: application/json
{
"source": "web|koreader|kobo|mobile",
"location": {
"percentage": 0.45678,
"epubcfi": "epubcfi(/6/4/2:15)",
"character": 15432,
"chapter": 3,
"page": 89,
"total_pages": 200
},
"device_metadata": {
"device_type": "web",
"user_agent": "..."
}
}
```
## Conflicts
### List Conflicts
```http
GET /api/conflicts?status=unresolved&type=progress
Authorization: Bearer <token>
```
**Query Parameters**:
- `status`: "unresolved|all" (default: "unresolved")
- `type`: "progress|note|highlight|all" (default: "all")
**Response** (200):
```json
{
"conflicts": [
{
"id": "conflict-uuid",
"media_item_id": "book-uuid",
"media_item_title": "Book Title",
"conflict_type": "progress",
"conflict_data": {
"koreader": {
"source": "koreader",
"timestamp": "2026-01-30T20:10:00Z",
"data": {
"percentage": 0.45,
"epubcfi": "epubcfi(/6/4/2:15)",
"character": 15432
}
},
"kobo": {
"source": "kobo",
"timestamp": "2026-01-30T20:05:00Z",
"data": {
"percentage": 0.42
}
}
},
"resolution_status": "unresolved",
"created_at": "2026-01-30T20:10:05Z"
}
],
"total": 1,
"unresolved": 1
}
```
### Get Conflict Details
```http
GET /api/conflicts/{conflict_id}
Authorization: Bearer <token>
```
### Resolve Conflict
```http
POST /api/conflicts/{conflict_id}/resolve
Authorization: Bearer <token>
Content-Type: application/json
{
"winner": "koreader|kobo|web|manual",
"manual_data": {
"percentage": 0.43,
"epubcfi": "epubcfi(/6/4/2:20)",
"character": 15500
},
"apply_to_all_future_conflicts": false,
"reason": "user chose more recent progress"
}
```
**Response** (200):
```json
{
"conflict_resolved": true,
"applied_to": {
"progress": true,
"annotations": false
},
"devices_synced": ["device-1", "device-2"]
}
```
### Delete Conflict
```http
DELETE /api/conflicts/{conflict_id}
Authorization: Bearer <token>
```
### Dismiss All Resolved
```http
DELETE /api/conflicts/dismiss-resolved
Authorization: Bearer <token>
```
## Sync Queue
### List Queue Items
```http
GET /api/queue/items?limit=50&offset=0
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"items": [
{
"id": "uuid",
"device_id": "device-uuid",
"device_name": "My Kobo",
"media_item_id": "book-uuid",
"sync_type": "progress",
"sync_data": {},
"priority": 5,
"attempts": 0,
"max_attempts": 3,
"status": "pending",
"error_message": null,
"created_at": "2026-01-31T10:00:00Z"
}
],
"total": 100
}
```
### Process Queue Item
```http
POST /api/queue/items/{queue_item_id}/process
Authorization: Bearer <token>
```
### Retry Queue Item
```http
POST /api/queue/items/{queue_item_id}/retry
Authorization: Bearer <token>
```
### Delete Queue Item
```http
DELETE /api/queue/items/{queue_item_id}
Authorization: Bearer <token>
```
### Clear Queue
```http
DELETE /api/queue/clear
Authorization: Bearer <token>
```
### Clear Failed Items
```http
DELETE /api/queue/clear-failed
Authorization: Bearer <token>
```
### Get Queue Stats
```http
GET /api/queue/stats
Authorization: Bearer <token>
```
**Response** (200):
```json
{
"pending": 15,
"processing": 2,
"failed": 3,
"completed": 100,
"total": 120
}
```
## WebSocket
### Connect to WebSocket
```
WS /ws/sync?token=<token>
```
### Message Format
**Client → Server (Heartbeat)**:
```json
{
"type": "ping"
}
```
**Server → Client (Progress Update)**:
```json
{
"type": "progress_update",
"timestamp": "2026-01-31T10:00:00Z",
"data": {
"book_id": "uuid",
"progress": {
"percentage": 0.45678,
"epubcfi": "epubcfi(/6/4/2:15)",
"chapter": 3
},
"annotations": {}
},
"source_device": {
"id": "device-uuid",
"name": "My Kobo",
"type": "kobo"
}
}
```
**Server → Client (Conflict Detected)**:
```json
{
"type": "conflict",
"timestamp": "2026-01-31T10:00:00Z",
"data": {
"book_id": "uuid",
"conflict_id": "uuid",
"conflict_type": "progress"
}
}
```
**Server → Client (Pong)**:
```json
{
"type": "pong"
}
```
## Error Responses
All endpoints return standardized error responses:
```json
{
"error": "Error message",
"message": "Detailed error information (if available)",
"code": "ERROR_CODE"
}
```
### HTTP Status Codes
- **200**: OK - Request successful
- **201**: Created - Resource created successfully
- **204**: No Content - Successful deletion or update with no content
- **400**: Bad Request - Invalid request parameters
- **401**: Unauthorized - Missing or invalid authentication
- **403**: Forbidden - Insufficient permissions
- **404**: Not Found - Resource does not exist
- **409**: Conflict - Resource conflict (e.g., duplicate)
- **422**: Unprocessable Entity - Validation error
- **429**: Too Many Requests - Rate limit exceeded
- **500**: Internal Server Error - Server error
### Rate Limiting
**Per-Device Limits**:
- Sync requests: 60/minute
- Progress updates: 120/minute
- Metadata requests: 30/minute
**Per-User Limits**:
- All requests: 300/minute
- Conflict resolutions: 10/minute
- Device registrations: 5/hour
**Rate Limit Headers**:
```
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 60
```
## Bruno v3.0 Collections
Complete API test collections are available in the `bruno/` directory:
```
bruno/
├── user/ # Authentication & profiles
├── admin/ # Admin operations
├── library/ # Library management
├── media-items/ # Media content
├── progress/ # Reading progress
├── notes/ # Notes API
├── highlights/ # Highlights API
├── ratings/ # Ratings API
├── devices/ # Device management
├── sync-koreader/ # KOReader sync protocol
├── sync-kobo/ # Kobo sync protocol
├── conflicts/ # Conflict resolution
├── queue/ # Sync queue management
└── collection.bru # Main collection file
```
## Testing with Bruno
Install Bruno CLI:
```bash
npm install -g @usebruno/cli
```
Run all tests:
```bash
bruno run
```
Run specific collection:
```bash
bruno run bruno/devices/
```
## Additional Resources
- [README.md](README.md) - Getting started guide
- [UNIVERSAL_SYNC_IMPLEMENTATION_GUIDE.md](UNIVERSAL_SYNC_IMPLEMENTATION_GUIDE.md) - Sync architecture
- [KOBOREADER_SETUP.md](KOBOREADER_SETUP.md) - KOReader device setup
- [KOBO_SETUP.md](KOBO_SETUP.md) - Kobo device setup
---
**Document Version**: 1.0
**Last Updated**: 2026-01-31
**API Version**: v1.0