bruno: regenerate collection from canonical docs + permanent docs↔Bruno parity gate

WO4. The old collection was the drifted artifact AGENTS.md warned about:
bodies pre-truncated at their first {{var}}, routes renamed server-side
but not here (/api/notes/:media_id, /api/book-matching/search,
/api/collections/:id/rules/test), an orphan universal-progress suite for
routes that no longer exist.

scripts/gen_bruno_from_docs.py emits one OpenCollection request per
frontmatter'd endpoint doc (191 requests): gate-verified example-request
JSON as body, {{env}} params per the environment file, device-token
routes get an explicit Bearer {{device_token}} header, login/register/
refresh keep the token-capture after-response scripts that chain the
collection. Old 1:1 mirrors deleted (128); hand-authored NewDevDBSetup,
environments, and edge-case suites kept. 30 scenario files with
truncated bodies (the documented corruption) deleted — they could not
run.

internal/docs/drift_test.go gains TestBrunoMirrorsDocs: every
frontmatter'd doc must have a Bruno request with matching method+URL
shape — the collection can never silently drift from the docs again.
opencollection.yml README block replaced with the real run instructions.

make test green (9 pkgs, parity test included in internal/docs).
This commit is contained in:
John O'Keefe committed 2026-10-24 18:35:51 -04:00
1 parent 6fc501b35a
commit acd82fff0a
353 files changed
+5165 -5293

No files matched your search

-30
View File
@@ -1,30 +0,0 @@
info:
name: Get Admin Library
type: http
seq: 5
http:
method: GET
url: '{{base_url}}/admin/library'
auth: inherit
body:
type: none
docs: |-
## Get Admin Library Page
Retrieves the admin library page for administrative access.
**Method:** GET
**Endpoint:** /admin/library
**Headers:**
- `Authorization` (string): Bearer token
**Response:**
- HTML content for the admin library page
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 403: Forbidden
+21
View File
@@ -0,0 +1,21 @@
info:
name: Hash Conflicts (API)
type: http
seq: 1
http:
method: GET
url: '{{base_url}}/api/admin/hash-conflicts'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Hash Conflicts (API)
`GET /api/admin/hash-conflicts` — auth: jwt+admin.
Canonical reference: `docs/developer/api/admin/hash_conflicts_api.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: List Users
type: http
seq: 2
http:
method: GET
url: '{{base_url}}/api/auth/users'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# List Users
`GET /api/auth/users` — auth: jwt+admin.
Canonical reference: `docs/developer/api/admin/list_users.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+27
View File
@@ -0,0 +1,27 @@
info:
name: Update User Max Devices
type: http
seq: 3
http:
method: PUT
url: '{{base_url}}/api/auth/users/{{media_item_id}}/max-devices'
auth: inherit
body:
type: json
data: |-
{
"max_devices": 15
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Update User Max Devices
`PUT /api/auth/users/:id/max-devices` — auth: jwt+admin.
Canonical reference: `docs/developer/api/admin/update_user_max_devices.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
-47
View File
@@ -1,47 +0,0 @@
info:
name: Get Device Usage
type: http
seq: 2
http:
method: GET
url: '{{base_url}}/api/analytics/device-usage'
auth: inherit
body:
type: none
runtime:
scripts:
- type: tests
code: "test(\"status must be 200\", function() {\n expect(res.status).to.eql(200);"
docs: |-
Get usage statistics for all devices.
**Endpoint**: GET /api/analytics/device-usage
**Auth**: Required (Bearer token)
## Response Fields
| Field | Type | Description |
|-------|------|-------------|
| devices | array | List of device usage statistics |
| devices[].id | string | Device ID |
| devices[].device_name | string | Device name |
| devices[].device_type | string | Device type (kobo, kindle, koreader) |
| devices[].sync_count | int | Number of sync operations |
| devices[].last_sync | string | Last sync timestamp |
| devices[].total_reading_minutes | int | Total reading time on device |
| devices[].books_read | int | Number of books completed on device |
## Example Response
```json
{
"devices": [
{
"id": "789e4567-e89b-12d3-a456-426614174001",
"device_name": "My Kobo Clara",
"device_type": "kobo",
"sync_count": 45,
"last_sync": "2026-02-08T17:25:00Z",
"total_reading_minutes": 1250,
"books_read": 3
-57
View File
@@ -1,57 +0,0 @@
info:
name: Get Popular Books
type: http
seq: 1
http:
method: GET
url: '{{base_url}}/api/analytics/popular-books?limit=10'
auth: inherit
body:
type: none
runtime:
scripts:
- type: tests
code: "test(\"status must be 200\", function() {\n expect(res.status).to.eql(200);"
docs: |-
Get popular books sorted by read count.
**Endpoint**: GET /api/analytics/popular-books
**Auth**: Required (Bearer token)
## Query Parameters
| Parameter | Type | Required | Description |
|-----------|------|-----------|-------------|
| limit | int | No | Maximum number of books to return (default: 10) |
## Response Fields
| Field | Type | Description |
|-------|------|-------------|
| books | array | List of popular books |
| books[].media_item_id | string | Book ID |
| books[].title | string | Book title |
| books[].author | string | Book author |
| books[].read_count | int | Number of times read |
| books[].avg_completion | float | Average completion rate (0-1) |
| books[].cover_url | string | Cover image URL |
## Example Request
```
GET /api/analytics/popular-books?limit=10
```
## Example Response
```json
{
"books": [
{
"media_item_id": "323e4567-e89b-12d3-a456-426614174002",
"title": "The Great Gatsby",
"author": "F. Scott Fitzgerald",
"read_count": 5,
"avg_completion": 0.85,
"cover_url": "/api/books/323e4567-e89b-12d3-a456-426614174002/cover"
@@ -1,58 +0,0 @@
info:
name: Get Reading Stats Date Range
type: http
seq: 3
http:
method: GET
url: '{{base_url}}/api/analytics/reading-stats?start_date=2024-01-01&end_date=2024-01-31'
auth: inherit
body:
type: none
runtime:
scripts:
- type: tests
code: "test(\"status must be 200\", function() {\n expect(res.status).to.eql(200);"
docs: |-
Get reading statistics for a specific date range.
**Endpoint**: GET /api/analytics/reading-stats
**Auth**: Required (Bearer token)
## Query Parameters
| Parameter | Type | Required | Description |
|-----------|------|-----------|-------------|
| start_date | string | No | Start date (ISO 8601 format, default: 30 days ago) |
| end_date | string | No | End date (ISO 8601 format, default: today) |
## Response Fields
| Field | Type | Description |
|-------|------|-------------|
| total_books_read | int | Total books completed in range |
| total_pages_read | int | Total pages read in range |
| total_reading_time_minutes | int | Total reading time in minutes |
| completion_rate | float | Percentage of books completed (0-1) |
| daily_reading_minutes | array | Daily reading time per day |
| daily_reading_minutes[].date | string | Date (ISO 8601) |
| daily_reading_minutes[].minutes | int | Minutes read on that date |
## Example Request
```
GET /api/analytics/reading-stats?start_date=2024-01-01&end_date=2024-01-31
```
## Example Response
```json
{
"total_books_read": 2,
"total_pages_read": 450,
"total_reading_time_minutes": 720,
"completion_rate": 0.85,
"daily_reading_minutes": [
{
"date": "2024-01-01",
"minutes": 30
-58
View File
@@ -1,58 +0,0 @@
info:
name: Get Reading Stats
type: http
seq: 4
http:
method: GET
url: '{{base_url}}/api/analytics/reading-stats'
auth: inherit
body:
type: none
runtime:
scripts:
- type: tests
code: "test(\"status must be 200\", function() {\n expect(res.status).to.eql(200);"
docs: |-
Get overall reading statistics for the authenticated user.
**Endpoint**: GET /api/analytics/reading-stats
**Auth**: Required (Bearer token)
## Query Parameters
| Parameter | Type | Required | Description |
|-----------|------|-----------|-------------|
| start_date | string | No | Start date (ISO 8601 format) |
| end_date | string | No | End date (ISO 8601 format) |
## Response Fields
| Field | Type | Description |
|-------|------|-------------|
| total_books_read | int | Total books completed |
| total_pages_read | int | Total pages read |
| total_reading_time_minutes | int | Total reading time in minutes |
| completion_rate | float | Average book completion rate (0-1) |
| daily_reading_minutes | array | Daily reading time breakdown |
| daily_reading_minutes[].date | string | Date (ISO 8601) |
| daily_reading_minutes[].minutes | int | Minutes read on that date |
## Example Request
```
GET /api/analytics/reading-stats
```
## Example Response
```json
{
"total_books_read": 12,
"total_pages_read": 3450,
"total_reading_time_minutes": 5400,
"completion_rate": 0.78,
"daily_reading_minutes": [
{
"date": "2026-01-15",
"minutes": 45
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Reading Statistics
type: http
seq: 1
http:
method: GET
url: '{{base_url}}/api/analytics/reading-stats'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Reading Statistics
`GET /api/analytics/reading-stats` — auth: jwt.
Canonical reference: `docs/developer/api/analytics/get_analytics.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Device Usage
type: http
seq: 2
http:
method: GET
url: '{{base_url}}/api/analytics/device-usage'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Device Usage
`GET /api/analytics/device-usage` — auth: jwt.
Canonical reference: `docs/developer/api/analytics/get_device_usage.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Popular Books
type: http
seq: 3
http:
method: GET
url: '{{base_url}}/api/analytics/popular-books'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Popular Books
`GET /api/analytics/popular-books` — auth: jwt.
Canonical reference: `docs/developer/api/analytics/get_popular_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+51
View File
@@ -0,0 +1,51 @@
info:
name: Login User
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/auth/login'
auth: inherit
body:
type: json
data: |-
{
"login": "user@example.com",
"password": "SecureP@ss123!"
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
runtime:
scripts:
- type: after-response
code: |-
function onResponse(res) {
try {
const responseBody = res.getBody();
const token = responseBody.access_token;
const refreshToken = responseBody.refresh_token;
if (token) {
bru.setEnvVar("token", token, { persist: true });
console.log("Access token saved");
}
if (refreshToken) {
bru.setEnvVar("refresh_token", refreshToken, { persist: true });
console.log("Refresh token saved");
}
} catch (error) {
console.error("Token capture failed:", error.message);
}
}
onResponse();
docs: |-
# Login User
`POST /api/auth/login` — auth: none.
Canonical reference: `docs/developer/api/authentication/login.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Logout User
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/auth/logout'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Logout User
`POST /api/auth/logout` — auth: none.
Canonical reference: `docs/developer/api/authentication/logout.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Password Rules
type: http
seq: 3
http:
method: GET
url: '{{base_url}}/api/auth/password-rules'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Password Rules
`GET /api/auth/password-rules` — auth: none.
Canonical reference: `docs/developer/api/authentication/password_rules.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+50
View File
@@ -0,0 +1,50 @@
info:
name: Refresh Token
type: http
seq: 4
http:
method: POST
url: '{{base_url}}/api/auth/refresh'
auth: inherit
body:
type: json
data: |-
{
"refresh_token": "d4f5g6h7..."
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
runtime:
scripts:
- type: after-response
code: |-
function onResponse(res) {
try {
const responseBody = res.getBody();
const token = responseBody.access_token;
const refreshToken = responseBody.refresh_token;
if (token) {
bru.setEnvVar("token", token, { persist: true });
console.log("Access token saved");
}
if (refreshToken) {
bru.setEnvVar("refresh_token", refreshToken, { persist: true });
console.log("Refresh token saved");
}
} catch (error) {
console.error("Token capture failed:", error.message);
}
}
onResponse();
docs: |-
# Refresh Token
`POST /api/auth/refresh` — auth: none.
Canonical reference: `docs/developer/api/authentication/refresh_token.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+54
View File
@@ -0,0 +1,54 @@
info:
name: Register User
type: http
seq: 5
http:
method: POST
url: '{{base_url}}/api/auth/register'
auth: inherit
body:
type: json
data: |-
{
"email": "user@example.com",
"username": "john",
"password": "SecureP@ss123!",
"first_name": "John",
"last_name": "Doe"
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
runtime:
scripts:
- type: after-response
code: |-
function onResponse(res) {
try {
const responseBody = res.getBody();
const token = responseBody.access_token;
const refreshToken = responseBody.refresh_token;
if (token) {
bru.setEnvVar("token", token, { persist: true });
console.log("Access token saved");
}
if (refreshToken) {
bru.setEnvVar("refresh_token", refreshToken, { persist: true });
console.log("Refresh token saved");
}
} catch (error) {
console.error("Token capture failed:", error.message);
}
}
onResponse();
docs: |-
# Register User
`POST /api/auth/register` — auth: none.
Canonical reference: `docs/developer/api/authentication/register.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+28
View File
@@ -0,0 +1,28 @@
info:
name: Auto Link Books
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/sync/auto-link-books'
auth: inherit
body:
type: json
data: |-
{
"confidence_threshold": 0.8,
"limit": 100
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Auto Link Books
`POST /api/sync/auto-link-books` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/auto_link_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+38
View File
@@ -0,0 +1,38 @@
info:
name: Bulk Link Books
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/sync/bulk-link-books'
auth: inherit
body:
type: json
data: |-
{
"links": [
{
"unlinked_book_id": "770e8400-e29b-41d4-a716-446655440002",
"media_item_id": "660e8400-e29b-41d4-a716-446655440001",
"confidence_score": 0.95
},
{
"unlinked_book_id": "880e8400-e29b-41d4-a716-446655440003",
"media_item_id": "990e8400-e29b-41d4-a716-446655440004",
"confidence_score": 0.87
}
]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Bulk Link Books
`POST /api/sync/bulk-link-books` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/bulk_link_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,29 @@
info:
name: Create Device File Alias
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/devices/{{device_id}}/file-aliases'
auth: inherit
body:
type: json
data: |-
{
"media_item_id": "550e8400-e29b-41d4-a716-446655440001",
"file_name": "book.epub",
"file_hash": "abc123..."
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Create Device File Alias
`POST /api/devices/:id/file-aliases` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/create_device_file_alias.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Delete Device File Alias
type: http
seq: 4
http:
method: DELETE
url: '{{base_url}}/api/devices/{{device_id}}/file-aliases/{{alias_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Delete Device File Alias
`DELETE /api/devices/:id/file-aliases/:aliasId` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/delete_device_file_alias.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Book Matches
type: http
seq: 5
http:
method: GET
url: '{{base_url}}/api/books/match'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Book Matches
`GET /api/books/match` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/get_book_matches.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Get Device File Aliases
type: http
seq: 6
http:
method: GET
url: '{{base_url}}/api/devices/{{device_id}}/file-aliases'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Device File Aliases
`GET /api/devices/:id/file-aliases` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/get_device_file_aliases.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Get Unlinked Book Suggestions
type: http
seq: 7
http:
method: GET
url: '{{base_url}}/api/sync/unlinked-books/{{unlinked_book_id}}/suggestions'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Unlinked Book Suggestions
`GET /api/sync/unlinked-books/:id/suggestions` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/get_unlinked_book_suggestions.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Get Unlinked Books
type: http
seq: 8
http:
method: GET
url: '{{base_url}}/api/devices/{{device_id}}/sync/unlinked-books'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Unlinked Books
`GET /api/devices/:deviceId/sync/unlinked-books` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/get_unlinked_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+33
View File
@@ -0,0 +1,33 @@
info:
name: Link Book
type: http
seq: 9
http:
method: POST
url: '{{base_url}}/api/devices/{{device_id}}/sync/link-book'
auth: inherit
body:
type: json
data: |-
{
"device_file": {
"file_path": "/books/hobbit.epub",
"sha256": "abc123...",
"title": "The Hobbit"
},
"media_item_id": "550e8400-e29b-41d4-a716-446655440000",
"confidence_score": 1.0
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Link Book
`POST /api/devices/:deviceId/sync/link-book` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/link_book.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+31
View File
@@ -0,0 +1,31 @@
info:
name: Search Books for Matching
type: http
seq: 10
http:
method: POST
url: '{{base_url}}/api/sync/books/query'
auth: inherit
body:
type: json
data: |-
{
"identifiers": ["isbn:978-0345391802", "uuid:abc-123"],
"sha256": "a1b2c3d4e5f6abc123...",
"title": "The Hobbit",
"author": "J.R.R. Tolkien",
"file_size": 2456789
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Search Books for Matching
`POST /api/sync/books/query` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/search_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,28 @@
info:
name: Update Device File Alias
type: http
seq: 11
http:
method: PUT
url: '{{base_url}}/api/devices/{{device_id}}/file-aliases/{{alias_id}}'
auth: inherit
body:
type: json
data: |-
{
"file_name": "updated-book.epub",
"file_hash": "newhash123..."
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Update Device File Alias
`PUT /api/devices/:id/file-aliases/:aliasId` — auth: jwt.
Canonical reference: `docs/developer/api/book-matching/update_device_file_alias.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
-15
View File
@@ -1,15 +0,0 @@
info:
name: Bulk Delete Media Items
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/media-items/bulk-delete'
auth: inherit
body:
type: json
data: "{\n \"media_item_ids\": [\n \"{{bookId1"
headers:
- key: Content-Type
value: application/json
docs: "## Bulk Delete Media Items\n\n Deletes multiple media items in a single request.\n\n **Method:** POST\n\n **Endpoint:** /api/media-items/bulk-delete\n\n **Authentication:** Required (Bearer token)\n\n **Request Body:**\n - `media_item_ids` (array of strings): Array of media item UUIDs to delete\n\n **Response:**\n - `results` (array): Results for each deletion attempt\n - `total` (number): Total number of media items processed\n - `deleted` (number): Number of successfully deleted media items\n - `failed` (number): Number of failed deletions\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"media_item_ids\": [\n \"uuid-1\",\n \"uuid-2\",\n \"uuid-3\"\n ]"
-16
View File
@@ -1,16 +0,0 @@
info:
name: Bulk Update Media Items
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/media-items/bulk-update'
auth: inherit
body:
type: json
data: "{\n \"media_item_updates\": [\n {\n \"media_item_id\": \"{{bookId1"
headers:
- key: Content-Type
value: application/json
docs: "## Bulk Update Media Items\n\n Updates multiple media items in a single request with different fields for each item.\n\n **Method:** POST\n\n **Endpoint:** /api/media-items/bulk-update\n\n **Authentication:** Required (Bearer token)\n\n **Request Body:**\n - `media_item_updates` (array): Array of update objects\n - `media_item_id` (string): Media item UUID to update\n - `updates` (object): Fields to update (can include title, author, genre, tags, contributors, etc.)\n\n **Response:**\n - `results` (array): Results for each update attempt\n - `total` (number): Total number of media items processed\n - `updated` (number): Number of successfully updated media items\n - `failed` (number): Number of failed updates\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"media_item_updates\": [\n {\n\
\ \"media_item_id\": \"uuid-1\",\n \"updates\": {\n \"title\": \"New Title\",\n \"genre\": \"Fiction\",\n \"tags\": [\"fiction\", \"adventure\"]"
@@ -1,12 +0,0 @@
info:
name: Add Books to Collection
type: http
seq: 6
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/books'
auth: inherit
body:
type: json
data: "{\n \"book_ids\": [\n \"{{book_id_1}}"
docs: "## Add Books to Collection\n\n Adds multiple books to an existing collection.\n \n **Method:** POST\n \n **Endpoint:** /api/collections/{collection_id}/books\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `collection_id` (string): Collection ID\n \n **Request Body:**\n - `book_ids` (array of strings, required): List of book IDs to add\n \n **Response:**\n - `message` (string): Success message\n - `added_count` (integer): Number of books added\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Collection not found"
-13
View File
@@ -1,13 +0,0 @@
info:
name: Create Collection
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/collections'
auth: inherit
body:
type: json
data: "{\n \"name\": \"Science Fiction\",\n \"description\": \"My favorite sci-fi books\",\n \"color\": \"#ff0000\",\n \"icon\": \"\U0001F680\",\n \"auto_assign_rules\": [\n {\n \"id\": \"rule-1\",\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"Science Fiction\",\n \"priority\": 8"
docs: "## Create Collection\n\n Creates a new collection for organizing books.\n \n **Method:** POST\n \n **Endpoint:** /api/collections\n \n **Authentication:** Required (Bearer token)\n \n **Request Body:**\n - `name` (string, required): Collection name (max 100 chars)\n - `description` (string, optional): Collection description (max 500 chars)\n - `color` (string, optional): Hex color code (default #3498db)\n - `icon` (string, optional): Emoji icon (default \U0001F4DA)\n - `auto_assign_rules` (array, optional): Auto-assignment rules\n - `id` (string): Rule ID\n - `field` (string): Field to match (genre, author, series, etc.)\n - `operator` (string): Comparison operator (equals, contains, startsWith)\n - `value` (string): Value to match\n - `priority` (integer): Rule priority (1-10)\n \n **Response:**\n - Collection object with generated ID\n \n **Status Codes:**\n - 201: Created\n - 400: Invalid request\n - 401: Unauthorized\n - 409: Collection name\
\ already exists"
@@ -1,12 +0,0 @@
info:
name: Create Device Mapping
type: http
seq: 9
http:
method: POST
url: '{{base_url}}/api/devices/{{device_id}}/collections'
auth: inherit
body:
type: json
data: "{\n \"collection_id\": \"{{collection_id}}"
docs: "## Create Device Mapping\n\n Maps a collection to a device shelf for synchronization.\n \n **Method:** POST\n \n **Endpoint:** /api/devices/{device_id}/collections\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `device_id` (string): Device ID\n \n **Request Body:**\n - `collection_id` (string, required): Collection ID to map\n - `device_shelf_name` (string, optional): Custom shelf name on device\n - `sync_direction` (string, optional): sync direction (book_to_device, device_to_book, bidirectional)\n \n **Response:**\n - Mapping object with generated ID\n \n **Status Codes:**\n - 201: Created\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Device or collection not found"
-30
View File
@@ -1,30 +0,0 @@
info:
name: Delete Collection
type: http
seq: 5
http:
method: DELETE
url: '{{base_url}}/api/collections/{{collection_id}}'
auth: inherit
docs: |-
## Delete Collection
Permanently deletes a collection and all its mappings.
**Method:** DELETE
**Endpoint:** /api/collections/{collection_id}
**Authentication:** Required (Bearer token)
**Path Parameters:**
- `collection_id` (string): Collection ID
**Response:**
- `message` (string): Success message
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Collection not found
@@ -1,31 +0,0 @@
info:
name: Delete Device Mapping
type: http
seq: 11
http:
method: DELETE
url: '{{base_url}}/api/devices/{{device_id}}/collections/{{mapping_id}}'
auth: inherit
docs: |-
## Delete Device Mapping
Removes a collection-to-device mapping.
**Method:** DELETE
**Endpoint:** /api/devices/{device_id}/collections/{mapping_id}
**Authentication:** Required (Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
- `mapping_id` (string): Mapping ID
**Response:**
- `message` (string): Success message
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Device or mapping not found
@@ -1,44 +0,0 @@
info:
name: Get Book Collections
type: http
seq: 12
http:
method: GET
url: "{{base_url}}/api/collections/{{collection_id}}/books"
body:
type: json
data: |-
{
"collection_id": "{{collection_id}}"
}
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
## Get Book Collections
Retrieves all collections that a specific book belongs to.
**Method:** GET
**Endpoint:** /api/collections/{{collection_id}}/books
**Authentication:** Required (Bearer token)
**Query Parameters:**
- `book_id` (string, required): Book ID
**Response:**
- Array of collection objects containing the book
**Status Codes:**
- 200: Success
- 400: Invalid request
- 401: Unauthorized
- 404: Book not found
-30
View File
@@ -1,30 +0,0 @@
info:
name: Get Collection
type: http
seq: 3
http:
method: GET
url: '{{base_url}}/api/collections/{{collection_id}}'
auth: inherit
docs: |-
## Get Collection
Retrieves detailed information about a specific collection.
**Method:** GET
**Endpoint:** /api/collections/{collection_id}
**Authentication:** Required (Bearer token)
**Path Parameters:**
- `collection_id` (string): Collection ID
**Response:**
- Collection object with all fields including books, rules, and mappings
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Collection not found
-31
View File
@@ -1,31 +0,0 @@
info:
name: Get Collections
type: http
seq: 2
http:
method: GET
url: '{{base_url}}/api/collections?include_auto=true&sort_by=name'
auth: inherit
docs: |-
## Get Collections
Retrieves all collections for the authenticated user.
**Method:** GET
**Endpoint:** /api/collections
**Authentication:** Required (Bearer token)
**Query Parameters:**
- `include_auto` (boolean, optional): Include auto-created collections (default false)
- `sort_by` (string, optional): Sort field (name, created_at, book_count)
- `order` (string, optional): Sort order (asc, desc)
**Response:**
- Array of collection objects
**Status Codes:**
- 200: Success
- 401: Unauthorized
-30
View File
@@ -1,30 +0,0 @@
info:
name: Get Device Mappings
type: http
seq: 8
http:
method: GET
url: '{{base_url}}/api/devices/{{device_id}}/collections'
auth: inherit
docs: |-
## Get Device Mappings
Retrieves all collection mappings for a specific device.
**Method:** GET
**Endpoint:** /api/devices/{device_id}/collections
**Authentication:** Required (Bearer token)
**Path Parameters:**
- `device_id` (string): Device ID
**Response:**
- Array of device mapping objects with collection details
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Device not found
@@ -1,31 +0,0 @@
info:
name: Remove Book from Collection
type: http
seq: 7
http:
method: DELETE
url: '{{base_url}}/api/collections/{{collection_id}}/books/{{book_id}}'
auth: inherit
docs: |-
## Remove Book from Collection
Removes a single book from a collection.
**Method:** DELETE
**Endpoint:** /api/collections/{collection_id}/books/{book_id}
**Authentication:** Required (Bearer token)
**Path Parameters:**
- `collection_id` (string): Collection ID
- `book_id` (string): Book ID
**Response:**
- `message` (string): Success message
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 404: Collection or book not found
-12
View File
@@ -1,12 +0,0 @@
info:
name: Update Collection
type: http
seq: 4
http:
method: PUT
url: '{{base_url}}/api/collections/{{collection_id}}'
auth: inherit
body:
type: json
data: "{\n \"name\": \"Sci-Fi Favorites\",\n \"description\": \"Updated description\",\n \"color\": \"#00ff00\",\n \"icon\": \"⭐\",\n \"auto_assign_rules\": [\n {\n \"id\": \"rule-2\",\n \"field\": \"series\",\n \"operator\": \"equals\",\n \"value\": \"Foundation\",\n \"priority\": 9"
docs: "## Update Collection\n\n Updates an existing collection's properties.\n \n **Method:** PUT\n \n **Endpoint:** /api/collections/{collection_id}\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `collection_id` (string): Collection ID\n \n **Request Body:**\n - `name` (string, optional): New collection name\n - `description` (string, optional): New description\n - `color` (string, optional): New hex color code\n - `icon` (string, optional): New emoji icon\n - `auto_assign_rules` (array, optional): Updated auto-assignment rules\n \n **Response:**\n - Updated collection object\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Collection not found\n - 409: Collection name already exists"
@@ -1,12 +0,0 @@
info:
name: Update Device Mapping
type: http
seq: 10
http:
method: PUT
url: '{{base_url}}/api/devices/{{device_id}}/collections/{{mapping_id}}'
auth: inherit
body:
type: json
data: "{\n \"device_shelf_name\": \"Science Fiction\",\n \"sync_direction\": \"book_to_device\""
docs: "## Update Device Mapping\n\n Updates settings for a collection-to-device mapping.\n \n **Method:** PUT\n \n **Endpoint:** /api/devices/{device_id}/collections/{mapping_id}\n \n **Authentication:** Required (Bearer token)\n \n **Path Parameters:**\n - `device_id` (string): Device ID\n - `mapping_id` (string): Mapping ID\n \n **Request Body:**\n - `device_shelf_name` (string, optional): Custom shelf name on device\n - `sync_direction` (string, optional): Sync direction (book_to_device, device_to_book, bidirectional)\n \n **Response:**\n - Updated mapping object\n \n **Status Codes:**\n - 200: Success\n - 400: Invalid request\n - 401: Unauthorized\n - 404: Device or mapping not found"
@@ -0,0 +1,31 @@
info:
name: Add Auto-Assign Rule
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/rules'
auth: inherit
body:
type: json
data: |-
{
"field": "genre",
"operator": "equals",
"value": "Science Fiction",
"priority": 1,
"enabled": true
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Add Auto-Assign Rule
`POST /api/collections/:id/rules` — auth: jwt.
Canonical reference: `docs/developer/api/collections/add_auto_assign_rule.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+34
View File
@@ -0,0 +1,34 @@
info:
name: Bulk Add Books
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/collections/bulk-add-books'
auth: inherit
body:
type: json
data: |-
{
"operations": [
{
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
"book_ids": [
"660e8400-e29b-41d4-a716-446655440001"
]
}
]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Bulk Add Books
`POST /api/collections/bulk-add-books` — auth: jwt.
Canonical reference: `docs/developer/api/collections/bulk_add_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+31
View File
@@ -0,0 +1,31 @@
info:
name: Bulk Assign Books
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/books'
auth: inherit
body:
type: json
data: |-
{
"book_ids": [
"660e8400-e29b-41d4-a716-446655440000",
"770e8400-e29b-41d4-a716-446655440000",
"880e8400-e29b-41d4-a716-446655440000"
]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Bulk Assign Books
`POST /api/collections/:id/books` — auth: jwt.
Canonical reference: `docs/developer/api/collections/bulk_assign.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+30
View File
@@ -0,0 +1,30 @@
info:
name: Bulk Remove Books
type: http
seq: 4
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/books/bulk-remove'
auth: inherit
body:
type: json
data: |-
{
"book_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Bulk Remove Books
`POST /api/collections/:id/books/bulk-remove` — auth: jwt.
Canonical reference: `docs/developer/api/collections/bulk_remove_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -1,16 +0,0 @@
meta:
name: Create Custom Section with Manual Books
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/collections'
auth: inherit
body:
type: json
data: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"My Favorites\",\n \"icon\": \"⭐\",\n \"description\": \"My favorite books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"\",\n \"manual_book_ids\": [\"{{media_item_id}}\"],\n \"match_type\": \"all\"\n}"
runtime:
scripts:
- type: after-response
code: "function onResponse(res) {\ntest(\"status must be 201 with valid request\", function() {\n expect(res.status).to.eql(201);\n});\n\ntest(\"response contains collection id\", function() {\n expect(res.body.id).to.exist;\nexpect(res.body.id).to.be.a('string');\n });\n\ntest(\"response contains collection name\", function() {\n expect(res.body.name).to.eql(\"My Favorites\");\n });\n}"
docs: "Create a custom collection with manually selected books.\n\n**Endpoint**: POST /api/collections\n**Auth**: Required (Bearer token)\n\n## Example Request\n\n```json\n{\n \"library_id\": \"cc23c3a7-f8fb-451a-a78d-2a16df1b725a\",\n \"name\": \"My Favorites\",\n \"icon\": \"⭐\",\n \"description\": \"My favorite books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"\",\n \"manual_book_ids\": [\"book-id-1\", \"book-id-2\"],\n \"match_type\": \"all\"\n}\n```\n\n## Response\n\nReturns the created collection with generated ID."
@@ -1,19 +0,0 @@
meta:
name: Create Custom Section Missing Required Fields
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/collections'
auth: inherit
body:
type: json
data: "{\n \"library_id\": \"{{library_id}}\",\n \"icon\": \"\U0001F4DA\"\n}"
runtime:
scripts:
- type: after-response
code: "function onResponse(res) {\ntest(\"status must be 400 with missing name\", function() {\n expect(res.status).to.eql(400);\n});\n\ntest(\"response contains error message\", function() {\n expect(res.body.error).to.exist;\n });\n}"
docs: 'Test error handling when creating a collection without required fields.
**Expected Response**: 400 Bad Request with error message.'
@@ -1,17 +0,0 @@
meta:
name: Create Custom Section with Filter Rules
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/collections'
auth: inherit
body:
type: json
data: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"Science Fiction Books\",\n \"icon\": \"\U0001F680\",\n \"description\": \"All my sci-fi books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"{\\\"rules\\\":[{\\\"id\\\":\\\"rule1\\\",\\\"field\\\":\\\"genre\\\",\\\"operator\\\":\\\"equals\\\",\\\"value\\\":\\\"Sci-Fi\\\",\\\"priority\\\":1}]}\",\n \"manual_book_ids\": [],\n \"match_type\": \"all\"\n}"
runtime:
scripts:
- type: after-response
code: "function onResponse(res) {\ntest(\"status must be 201 with valid request\", function() {\n expect(res.status).to.eql(201);\n});\n\ntest(\"response contains collection id\", function() {\n expect(res.body.id).to.exist;\nexpect(res.body.id).to.be.a('string');\n });\n\ntest(\"response contains collection name\", function() {\n expect(res.body.name).to.eql(\"Science Fiction Books\");\n });\n\ntest(\"response contains is_system false\", function() {\n expect(res.body.is_system).to.exist;\n expect(res.body.is_system).to.eql(false);\n });\n}"
docs: "Create a custom collection with filter rules.\n\n**Endpoint**: POST /api/collections\n**Auth**: Required (Bearer token)\n\n## Request Body\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| library_id | string | Yes | Library UUID |\n| name | string | Yes | Collection name |\n| icon | string | No | Emoji icon |\n| description | string | No | Collection description |\n| show_on_dashboard | boolean | No | Show on dashboard (default: true) |\n| auto_assign_rules | string | No | JSON string of filter rules |\n| manual_book_ids | array | No | Array of book IDs |\n| match_type | string | No | \"all\" (AND) or \"any\" (OR) |\n\n## Example Request\n\n```json\n{\n \"library_id\": \"cc23c3a7-f8fb-451a-a78d-2a16df1b725a\",\n \"name\": \"Science Fiction Books\",\n \"icon\": \"\U0001F680\",\n \"description\": \"All my sci-fi books\",\n \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"{\\\"rules\\\":[{\\\"id\\\":\\\"rule1\\\",\\\"field\\\":\\\
\"genre\\\",\\\"operator\\\":\\\"equals\\\",\\\"value\\\":\\\"Sci-Fi\\\",\\\"priority\\\":1}]}\",\n \"manual_book_ids\": [],\n \"match_type\": \"all\"\n}\n```\n\n## Response\n\nReturns the created collection with generated ID."
+33
View File
@@ -0,0 +1,33 @@
info:
name: Create Collection
type: http
seq: 5
http:
method: POST
url: '{{base_url}}/api/collections'
auth: inherit
body:
type: json
data: |-
{
"name": "To Read",
"description": "Books I want to read soon",
"color": "#00FF00",
"icon": "📖",
"collection_type": "static",
"auto_assign_rules": [],
"view_settings": { "kobo": { "view_mode": "grid" } }
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Create Collection
`POST /api/collections` — auth: jwt.
Canonical reference: `docs/developer/api/collections/create_collection.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,29 @@
info:
name: Create Shelf Mapping
type: http
seq: 6
http:
method: POST
url: '{{base_url}}/api/devices/{{collection_id}}/collections'
auth: inherit
body:
type: json
data: |-
{
"collection_id": "550e8400-e29b-41d4-a716-446655440000",
"device_shelf_name": "Sci-Fi",
"sync_direction": "bidirectional"
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Create Shelf Mapping
`POST /api/devices/:id/collections` — auth: jwt.
Canonical reference: `docs/developer/api/collections/create_shelf_mapping.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Delete Collection
type: http
seq: 7
http:
method: DELETE
url: '{{base_url}}/api/collections/{{collection_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Delete Collection
`DELETE /api/collections/:id` — auth: jwt.
Canonical reference: `docs/developer/api/collections/delete_collection.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Delete Shelf Mapping
type: http
seq: 8
http:
method: DELETE
url: '{{base_url}}/api/devices/{{collection_id}}/collections/{{collection_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Delete Shelf Mapping
`DELETE /api/devices/:id/collections/:collectionId` — auth: jwt.
Canonical reference: `docs/developer/api/collections/delete_shelf_mapping.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Collection
type: http
seq: 9
http:
method: GET
url: '{{base_url}}/api/collections/{{collection_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Collection
`GET /api/collections/:id` — auth: jwt.
Canonical reference: `docs/developer/api/collections/get_collection.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Get Book Collections
type: http
seq: 10
http:
method: GET
url: '{{base_url}}/api/collections/{{collection_id}}/books'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Book Collections
`GET /api/collections/:id/books` — auth: jwt.
Canonical reference: `docs/developer/api/collections/get_collection_books.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: List Collections
type: http
seq: 11
http:
method: GET
url: '{{base_url}}/api/collections'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# List Collections
`GET /api/collections` — auth: jwt.
Canonical reference: `docs/developer/api/collections/list_collections.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
-16
View File
@@ -1,16 +0,0 @@
meta:
name: Preview Collection
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/collections/preview'
auth: inherit
body:
type: json
data: "{\n \"library_id\": \"{{library_id}}\",\n \"rules\": [\n{\n \"id\": \"rule1\",\n \"field\": \"genre\",\n\"operator\": \"equals\",\n \"value\": \"Fiction\",\n\"priority\": 1\n }\n ],\n \"manual_book_ids\": [],\n \"limit\": 20\n}"
runtime:
scripts:
- type: after-response
code: "function onResponse(res) {\ntest(\"status must be 200 with valid request\", function() {\n expect(res.status).to.eql(200);\n});\n\ntest(\"response contains items array\", function() {\n expect(res.body.items).to.exist;\nexpect(res.body.items).to.be.an('array');\n });\n}"
docs: "Preview collection with filter rules before saving.\n\n**Endpoint**: POST /api/collections/preview\n**Auth**: Required (Bearer token)\n\n## Request Body\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| library_id | string | Yes | Library UUID |\n| rules | array | No | Filter rules to evaluate |\n| manual_book_ids | array | No | Manually selected book IDs |\n| limit | int | No | Max items to return (default: 20, max: 100) |\n\n## Example Request\n\n```json\n{\n \"library_id\": \"cc23c3a7-f8fb-451a-a78d-2a16df1b725a\",\n \"rules\": [\n {\n \"id\": \"rule1\",\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"Fiction\",\n \"priority\": 1\n }\n ],\n \"manual_book_ids\": [],\n \"limit\": 20\n}\n```\n\n## Response\n\nReturns array of matching book items.\n\nThis endpoint is used by the Custom Section Builder to show users what books will match their filter rules before they save the collection."
+30
View File
@@ -0,0 +1,30 @@
info:
name: Preview Collection
type: http
seq: 12
http:
method: POST
url: '{{base_url}}/api/collections/preview'
auth: inherit
body:
type: json
data: |-
{
"library_id": "550e8400-e29b-41d4-a716-446655440000",
"rules": [],
"manual_book_ids": [],
"limit": 20
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Preview Collection
`POST /api/collections/preview` — auth: jwt.
Canonical reference: `docs/developer/api/collections/preview_collection.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Remove Auto-Assign Rule
type: http
seq: 13
http:
method: DELETE
url: '{{base_url}}/api/collections/{{collection_id}}/rules/{{rule_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Remove Auto-Assign Rule
`DELETE /api/collections/:id/rules/:ruleId` — auth: jwt.
Canonical reference: `docs/developer/api/collections/remove_auto_assign_rule.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+30
View File
@@ -0,0 +1,30 @@
info:
name: Reorder Collection Items
type: http
seq: 14
http:
method: PUT
url: '{{base_url}}/api/collections/{{collection_id}}/items/order'
auth: inherit
body:
type: json
data: |-
{
"item_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Reorder Collection Items
`PUT /api/collections/:id/items/order` — auth: jwt.
Canonical reference: `docs/developer/api/collections/reorder_items.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -1,18 +0,0 @@
info:
name: Bulk Add Books to Collections
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/collections/bulk-add-books'
auth: inherit
body:
type: json
data: "{\n \"operations\": [\n {\n \"collection_id\": \"{{collectionId1"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Bulk Add Books to Collections\n\n Adds multiple books to multiple collections in a single request. Each operation specifies a collection and a list of books to add.\n\n **Method:** POST\n\n **Endpoint:** /api/collections/bulk-add-books\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `operations` (array): Array of collection-book operations\n - `collection_id` (string): Collection UUID\n - `book_ids` (array): Array of book UUIDs to add to the collection\n\n **Response:**\n - `results` (array): Results for each operation\n - `total` (number): Total number of operations\n - `success` (number): Number of successful operations\n - `failed` (number): Number of failed operations\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"operations\": [\n {\n \"collection_id\":\
\ \"collection-uuid-1\",\n \"book_ids\": [\"book-1\", \"book-2\"]"
@@ -1,15 +0,0 @@
info:
name: Bulk Remove Books - All Books
type: http
seq: 5
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/books/bulk-remove'
auth: inherit
body:
type: json
data: "{\n \"book_ids\": [\n \"{{bookId1"
headers:
- key: Content-Type
value: application/json
docs: "## Bulk Remove Books from Collection\n\n Removes multiple books from a collection in a single request.\n\n **Method:** POST\n\n **Endpoint:** /api/collections/{collection_id"
@@ -1,17 +0,0 @@
info:
name: Bulk Remove Books - Empty List
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/books/bulk-remove'
auth: inherit
body:
type: json
data: "{\n \"book_ids\": []"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Bulk Remove Books - Empty List Validation\n\n Tests validation behavior when providing an empty book_ids array.\n\n **Expected Result:** 400 Bad Request\n\n **Validation Rule:** book_ids array must contain at least one book UUID.\n\n **Purpose:** Ensures the API properly validates input and rejects empty removal requests."
@@ -1,17 +0,0 @@
info:
name: Bulk Remove Books - Invalid IDs
type: http
seq: 4
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/books/bulk-remove'
auth: inherit
body:
type: json
data: "{\n \"book_ids\": [\n \"{{bookId1"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Bulk Remove Books - Invalid IDs\n\n Tests behavior when the book_ids array contains invalid UUID formats or non-existent books.\n\n **Expected Result:** 200 OK with partial success\n\n **Purpose:** Verifies that:\n - Invalid UUID formats don't crash the endpoint\n - Non-existent book IDs are handled gracefully\n - Valid IDs in the same request are still processed\n - Response includes detailed results showing which succeeded/failed\n\n **Note:** The endpoint should process all valid IDs and report failures for invalid ones, allowing clients to handle partial failures appropriately."
@@ -1,17 +0,0 @@
info:
name: Bulk Remove Books - Single Book
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/collections/{{collection_id}}/books/bulk-remove'
auth: inherit
body:
type: json
data: "{\n \"book_ids\": [\n \"{{bookId1"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Bulk Remove Books - Single Book\n\n Tests that bulk remove endpoint works correctly with a single book.\n\n **Expected Result:** 200 OK with removed: 1, total: 1\n\n **Purpose:** Verifies the bulk remove endpoint handles single-item arrays correctly, providing flexibility for clients to use the same endpoint for both single and multiple removals.\n\n **Note:** Using bulk remove for a single book is functionally equivalent to the single remove endpoint but allows for consistent error handling and response format."
@@ -1,17 +0,0 @@
info:
name: Test Collection Rules - Author Contains
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/collections/test-rules'
auth: inherit
body:
type: json
data: "{\n \"rules\": [\n {\n \"field\": \"author\",\n \"operator\": \"contains\",\n \"value\": \"Asimov\""
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Test Collection Rules - Author Contains\n\n Tests the \"contains\" operator on the author field to find books by a specific author (partial match).\n\n **Example Use Case:** Finding all books by an author whose name contains \"Asimov\" (e.g., \"Isaac Asimov\").\n\n **Operator:** `contains` - Matches if the field contains the specified value as a substring (case-insensitive typically).\n\n **Expected Result:** Returns all books where the author field contains \"Asimov\".\n\n **Purpose:** Demonstrates text-based partial matching for author searches, useful when you don't need the exact author name or want to find books by authors with similar names."
@@ -1,17 +0,0 @@
info:
name: Test Collection Rules - Copyright Year Greater Than
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/collections/test-rules'
auth: inherit
body:
type: json
data: "{\n \"rules\": [\n {\n \"field\": \"copyright_year\",\n \"operator\": \"greater_than\",\n \"value\": \"2000\""
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Test Collection Rules - Copyright Year Greater Than\n\n Tests the \"greater_than\" operator on the copyright_year field to find books published after a specific year.\n\n **Example Use Case:** Creating a \"Modern Books\" collection with books published after 2000.\n\n **Operator:** `greater_than` - Matches if the field value is greater than the specified value (numeric comparison).\n\n **Field:** `copyright_year` - The year the book was copyrighted/published.\n\n **Expected Result:** Returns all books with copyright_year greater than 2000 (i.e., published in 2001 or later).\n\n **Purpose:** Demonstrates numeric comparison operators for creating date-based collections, useful for organizing books by publication era."
@@ -1,17 +0,0 @@
info:
name: Test Collection Rules - Empty Rules Array
type: http
seq: 5
http:
method: POST
url: '{{base_url}}/api/collections/test-rules'
auth: inherit
body:
type: json
data: "{\n \"rules\": []"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Test Collection Rules - Empty Rules Array\n\n Tests validation behavior when providing an empty rules array.\n\n **Expected Result:** 400 Bad Request\n\n **Validation Rule:** rules array must contain at least one rule object.\n\n **Purpose:** Ensures the API properly validates input and rejects empty rule sets, preventing accidental queries that would return all books or cause performance issues.\n\n **Use Case:** Client-side validation should prevent sending empty rules, but the API should also validate to catch malformed requests."
@@ -1,17 +0,0 @@
info:
name: Test Collection Rules - No Matches
type: http
seq: 4
http:
method: POST
url: '{{base_url}}/api/collections/test-rules'
auth: inherit
body:
type: json
data: "{\n \"rules\": [\n {\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"NonExistentGenre123456\""
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Test Collection Rules - No Matches\n\n Tests behavior when collection rules don't match any books in the library.\n\n **Example Use Case:** Validating that a new genre name doesn't exist before creating a collection for it, or testing edge cases.\n\n **Expected Result:** 200 OK with empty matches array and total: 0\n\n **Purpose:** Verifies that the API handles zero-match scenarios gracefully:\n - Returns 200 (success) not 404\n - Returns empty array, not null\n - Returns total: 0 for clarity\n - No errors thrown for no results\n\n **Note:** An empty result set is a valid response and doesn't indicate an error. This allows users to test rules confidently before creating collections."
@@ -1,18 +0,0 @@
info:
name: Test Collection Rules
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/collections/test-rules'
auth: inherit
body:
type: json
data: "{\n \"rules\": [\n {\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"Science Fiction\""
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Test Collection Rules\n\n Tests collection rules against the library to see which books match, without creating a collection. Useful for previewing what books would be included in a collection with specific rules.\n\n **Method:** POST\n\n **Endpoint:** /api/collections/test-rules\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `rules` (array): Array of rule objects to test\n - `field` (string): Field to test (genre, author, copyright_year, tags, etc.)\n - `operator` (string): Comparison operator\n - `equals`: Exact match\n - `contains`: Contains substring (for text fields)\n - `greater_than`: Greater than (for numeric fields)\n - `less_than`: Less than (for numeric fields)\n - `not_equals`: Not equal to\n - `starts_with`: Starts with\n - `ends_with`: Ends with\n - `is_empty`: Field is empty or null\n - `is_not_empty`: Field is not empty and not null\n - `value` (string): Value to compare against (not required\
\ for is_empty/is_not_empty)\n\n **Response:**\n - `matches` (array): Array of matching books\n - `id` (string): Book UUID\n - `title` (string): Book title\n - `author` (string): Book author\n - `genre` (string): Book genre\n - Additional book metadata\n - `total` (number): Total number of matching books\n\n **Status Codes:**\n - 200: Success - returns matching books\n - 400: Invalid request (empty rules array, invalid field/operator)\n - 401: Unauthorized\n - 500: Internal server error\n\n **Example Request:**\n ```json\n {\n \"rules\": [\n {\n \"field\": \"genre\",\n \"operator\": \"equals\",\n \"value\": \"Science Fiction\""
+38
View File
@@ -0,0 +1,38 @@
info:
name: Test Rule
type: http
seq: 15
http:
method: POST
url: '{{base_url}}/api/collections/test-rules'
auth: inherit
body:
type: json
data: |-
{
"rules": [
{
"field": "genre",
"operator": "equals",
"value": "Science Fiction"
},
{
"field": "author",
"operator": "contains",
"value": "Asimov"
}
]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Test Rule
`POST /api/collections/test-rules` — auth: jwt.
Canonical reference: `docs/developer/api/collections/test_rule.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+34
View File
@@ -0,0 +1,34 @@
info:
name: Update Collection
type: http
seq: 16
http:
method: PUT
url: '{{base_url}}/api/collections/{{collection_id}}'
auth: inherit
body:
type: json
data: |-
{
"name": "To Read (2026)",
"description": "Updated description",
"color": "#33CCFF",
"icon": "📚",
"auto_assign_rules": [],
"view_settings": { "hide_finished": true },
"hide_finished": ["true"],
"show_up_next": ["true"]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Update Collection
`PUT /api/collections/:id` — auth: jwt.
Canonical reference: `docs/developer/api/collections/update_collection.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+27
View File
@@ -0,0 +1,27 @@
info:
name: Update Collection Rule
type: http
seq: 17
http:
method: PUT
url: '{{base_url}}/api/collections/{{collection_id}}/rules/{{rule_id}}'
auth: inherit
body:
type: json
data: |-
{
"enabled": false
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Update Collection Rule
`PUT /api/collections/:id/rules/:ruleId` — auth: jwt.
Canonical reference: `docs/developer/api/collections/update_rule.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
-22
View File
@@ -1,22 +0,0 @@
info:
name: Delete Conflict
type: http
seq: 4
http:
method: DELETE
url: '{{base_url}}/api/conflicts/{{conflict_id}}'
auth: inherit
body:
type: none
headers:
- key: Authorization
value: Bearer {{token
docs: |-
## Delete Conflict
Permanently deletes a specific conflict record from the system.
**Method:** DELETE
**Endpoint:** /api/conflicts/{conflict_id
-37
View File
@@ -1,37 +0,0 @@
info:
name: Dismiss All Resolved Conflicts
type: http
seq: 5
http:
method: POST
url: '{{base_url}}/api/conflicts/dismiss-all'
auth: inherit
body:
type: none
headers:
- key: Authorization
value: Bearer {{token
docs: |-
## Dismiss All Resolved Conflicts
Deletes all resolved conflicts for the authenticated user, cleaning up the conflict list.
**Method:** POST
**Endpoint:** /api/conflicts/dismiss-all
**Authentication:** Bearer token
**Response:**
- `deleted` (number): Number of conflict records that were deleted
**Status Codes:**
- 200: Success - conflicts deleted
- 401: Unauthorized
- 500: Internal server error
**Example Response:**
```json
{
"deleted": 5
-22
View File
@@ -1,22 +0,0 @@
info:
name: Get Conflict Details
type: http
seq: 2
http:
method: GET
url: '{{base_url}}/api/conflicts/{{conflict_id}}'
auth: inherit
body:
type: none
headers:
- key: Authorization
value: Bearer {{token
docs: |-
## Get Conflict Details
Retrieves detailed information about a specific conflict, including side-by-side comparison of conflicting data from all sources.
**Method:** GET
**Endpoint:** /api/conflicts/{conflict_id
-74
View File
@@ -1,74 +0,0 @@
info:
name: List Conflicts
type: http
seq: 1
http:
method: GET
url: '{{base_url}}/api/conflicts?status=unresolved'
auth: inherit
body:
type: none
headers:
- key: Authorization
value: Bearer {{token
docs: |-
## List Conflicts
Lists all sync conflicts for the authenticated user with optional filtering.
**Method:** GET
**Endpoint:** /api/conflicts
**Authentication:** Bearer token
**Query Parameters:**
- `status` (string, optional): Filter by resolution status
- `unresolved`: Only unresolved conflicts (default)
- `user_resolved`: Conflicts resolved by user
- `auto_resolved`: Automatically resolved conflicts
- `all`: All conflicts regardless of status
- `type` (string, optional): Filter by conflict type
- `progress`: Reading progress conflicts
- `note`: Bookmark/note conflicts
- `highlight`: Highlight conflicts
**Response:**
- `conflicts` (array): Array of conflict objects
- `total` (number): Total number of conflicts matching filters
- `unresolved` (number): Number of unresolved conflicts
**Each Conflict Object:**
- `id` (string): Conflict UUID
- `media_item_id` (string): Associated book UUID
- `media_item_title` (string): Book title
- `conflict_type` (string): Type of conflict (progress, note, highlight)
- `conflict_data` (object): Side-by-side comparison of conflicting data
- `koreader`: Data from KOReader device
- `kobo`: Data from Kobo device
- `web`: Data from web interface
- `resolution_status` (string): Current status (unresolved, user_resolved, auto_resolved)
- `created_at` (string): ISO 8601 timestamp when conflict was detected
**Status Codes:**
- 200: Success
- 401: Unauthorized
- 500: Internal server error
**Example Request:**
```
GET /api/conflicts?status=unresolved&type=progress
```
**Example Response:**
```json
{
"conflicts": [
{
"id": "conflict-uuid",
"media_item_id": "book-uuid",
"media_item_title": "Foundation",
"conflict_type": "progress",
"conflict_data": {
"koreader": { "percentage": 0.65, "epubcfi": "..."
-15
View File
@@ -1,15 +0,0 @@
info:
name: Resolve Conflict
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/conflicts/{{conflict_id}}/resolve'
auth: inherit
body:
type: json
data: "{\n \"winner\": \"koreader\",\n \"manual_data\": null,\n \"apply_to_all_future_conflicts\": false,\n \"reason\": \"User chose more recent progress\""
headers:
- key: Authorization
value: Bearer {{token
docs: "## Resolve Conflict\n\n Resolves a sync conflict by choosing which source to use for the conflicting data.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/{conflict_id"
@@ -0,0 +1,30 @@
info:
name: Bulk Dismiss Conflicts
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/conflicts/bulk-dismiss'
auth: inherit
body:
type: json
data: |-
{
"conflict_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
]
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Bulk Dismiss Conflicts
`POST /api/conflicts/bulk-dismiss` — auth: jwt.
Canonical reference: `docs/developer/api/conflicts/bulk_dismiss_conflicts.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,31 @@
info:
name: Bulk Resolve Conflicts
type: http
seq: 2
http:
method: POST
url: '{{base_url}}/api/conflicts/bulk-resolve'
auth: inherit
body:
type: json
data: |-
{
"conflict_ids": [
"550e8400-e29b-41d4-a716-446655440000",
"660e8400-e29b-41d4-a716-446655440001"
],
"strategy": "highest_progress"
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Bulk Resolve Conflicts
`POST /api/conflicts/bulk-resolve` — auth: jwt.
Canonical reference: `docs/developer/api/conflicts/bulk_resolve_conflicts.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Delete Conflict
type: http
seq: 3
http:
method: DELETE
url: '{{base_url}}/api/conflicts/{{media_item_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Delete Conflict
`DELETE /api/conflicts/:id` — auth: jwt.
Canonical reference: `docs/developer/api/conflicts/delete_conflict.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Dismiss All Resolved
type: http
seq: 4
http:
method: POST
url: '{{base_url}}/api/conflicts/dismiss-all'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Dismiss All Resolved
`POST /api/conflicts/dismiss-all` — auth: jwt.
Canonical reference: `docs/developer/api/conflicts/dismiss_all_resolved.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Conflict Details
type: http
seq: 5
http:
method: GET
url: '{{base_url}}/api/conflicts/{{media_item_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Conflict Details
`GET /api/conflicts/:id` — auth: jwt.
Canonical reference: `docs/developer/api/conflicts/get_conflict.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: List Conflicts
type: http
seq: 6
http:
method: GET
url: '{{base_url}}/api/conflicts'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# List Conflicts
`GET /api/conflicts` — auth: jwt.
Canonical reference: `docs/developer/api/conflicts/list_conflicts.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+27
View File
@@ -0,0 +1,27 @@
info:
name: Resolve Conflict
type: http
seq: 7
http:
method: POST
url: '{{base_url}}/api/conflicts/{{media_item_id}}/resolve'
auth: inherit
body:
type: json
data: |-
{
"winner": "kobo"
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Resolve Conflict
`POST /api/conflicts/:id/resolve` — auth: jwt.
Canonical reference: `docs/developer/api/conflicts/resolve_conflict.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -1,17 +0,0 @@
info:
name: Bulk Dismiss Conflicts
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/conflicts/bulk-dismiss'
auth: inherit
body:
type: json
data: "{\n \"conflict_ids\": [\n \"{{conflictId1"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Bulk Dismiss Conflicts\n\n Dismisses multiple sync conflicts without resolving them. This removes them from the conflict list while leaving the data unchanged.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/bulk-dismiss\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `conflict_ids` (array): Array of conflict UUIDs to dismiss\n\n **Response:**\n - `results` (array): Results for each dismissal\n - `total` (number): Total number of conflicts processed\n - `success` (number): Number of successfully dismissed conflicts\n - `failed` (number): Number of failed dismissals\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 404: One or more conflicts not found\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"conflict_ids\": [\"uuid-1\", \"uuid-2\"]"
@@ -1,18 +0,0 @@
info:
name: Bulk Resolve Conflicts
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/conflicts/bulk-resolve'
auth: inherit
body:
type: json
data: "{\n \"conflict_ids\": [\n \"{{conflictId1"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Bulk Resolve Conflicts\n\n Resolves multiple sync conflicts in a single request using a specified resolution strategy.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/bulk-resolve\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `conflict_ids` (array): Array of conflict UUIDs to resolve\n - `strategy` (string): Resolution strategy\n - `most_recent`: Use the most recently updated progress\n - `highest_progress`: Use the reading progress with the highest percent read\n - `server`: Always prefer server-side data\n - `device`: Always prefer device-side data\n\n **Response:**\n - `results` (array): Results for each conflict resolution\n - `total` (number): Total number of conflicts processed\n - `success` (number): Number of successfully resolved conflicts\n - `failed` (number): Number of failed resolutions\n\n **Status Codes:**\n - 200: Success (with partial results if some failed)\n - 400: Invalid request data\n - 401: Unauthorized\n\
\ - 403: Forbidden\n - 404: One or more conflicts not found\n - 500: Internal server error\n\n **Example:**\n ```json\n {\n \"conflict_ids\": [\"uuid-1\", \"uuid-2\", \"uuid-3\"],\n \"strategy\": \"most_recent\""
@@ -1,18 +0,0 @@
info:
name: Bulk Resolve with Highest Progress Strategy
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/conflicts/bulk-resolve'
auth: inherit
body:
type: json
data: "{\n \"conflict_ids\": [\n \"{{conflictId1"
headers:
- key: Content-Type
value: application/json
- key: Authorization
value: Bearer {{authToken
docs: "## Bulk Resolve with Highest Progress Strategy\n\n Resolves multiple sync conflicts using the \"highest_progress\" strategy, which keeps the reading progress with the highest percentage read.\n\n **Method:** POST\n\n **Endpoint:** /api/conflicts/bulk-resolve\n\n **Authentication:** Bearer token\n\n **Request Body:**\n - `conflict_ids` (array): Array of conflict UUIDs to resolve\n - `strategy` (string): Must be \"highest_progress\"\n\n **Response:**\n - `results` (array): Results for each conflict resolution\n - `total` (number): Total number of conflicts processed\n - `success` (number): Number of successfully resolved conflicts\n - `failed` (number): Number of failed resolutions\n\n **Status Codes:**\n - 200: Success\n - 400: Invalid request data\n - 401: Unauthorized\n - 403: Forbidden\n - 500: Internal server error\n\n **Note:** The highest progress strategy is ideal when you want to preserve the most reading progress across devices. Use this when you've been\
\ reading on multiple devices and want to keep the furthest position."
@@ -1,38 +0,0 @@
info:
name: Create Collection with Dashboard
type: http
seq: 4
http:
method: POST
url: '{{base_url}}/api/collections'
auth: inherit
runtime:
scripts:
- type: tests
code: "test(\"creates collection successfully\", function() {\n expect(res.status).to.eql(201);"
docs: |-
Create a new collection with dashboard visibility enabled.
**Endpoint**: POST /api/collections
**Auth**: Required (Bearer token)
## Request Body
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| name | string | Yes | Collection name |
| description | string | No | Collection description |
| color | string | No | Hex color code |
| icon | string | No | Icon emoji or name |
| show_on_dashboard | boolean | No | Show on dashboard (default: false) |
## Example Request
```json
{
"name": "My Favorites",
"description": "My favorite books",
"color": "#FF5733",
"icon": "⭐",
"show_on_dashboard": true
@@ -1,73 +0,0 @@
info:
name: Get Dashboard Sections
type: http
seq: 1
http:
method: GET
url: '{{base_url}}/api/dashboard/sections?library_id={{library_id}}'
auth: inherit
body:
type: none
runtime:
scripts:
- type: tests
code: "test(\"status must be 200 with auth\", function() {\n expect(res.status).to.eql(200);\n\
});\n\ntest(\"response contains sections array\", function() {\n expect(res.body.sections).to.exist;\n\
expect(res.body.sections).to.be.an('array');\n });"
docs: |-
Get all dashboard sections for a specific library.
**Endpoint**: GET /api/dashboard/sections
**Auth**: Required (Bearer token via auth: inherit)
## Query Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| library_id | string | Yes | Library UUID |
| limit | int | No | Items per section (default: 20, max: 100) |
## Response
Returns array of sections including:
- System collections (continue-reading, recently-added, recently-read, not-started)
- User collections marked with show_on_dashboard: true
## Section Structure
Each section has:
- `id`: Collection name (string)
- `is_system`: Boolean indicating if this is a system collection
- `title`: Display title
- `description`: Collection description
- `icon`: Emoji icon
- `items`: Array of BookInfo objects
- `view_all_url`: URL to view all items (system collections only)
- `priority`: Display order (lower numbers first)
## Example Response
```json
{
"sections": [
{
"id": "continue-reading",
"is_system": true,
"title": "Continue Reading",
"description": "Books you're currently reading (0 < progress < 1)",
"icon": "📖",
"items": [
{
"media_item_id": "uuid",
"title": "Book Title",
"author": "Author Name",
"cover_image_path": "/path/to/cover.jpg"
}
],
"view_all_url": "/section/continue-reading",
"priority": 1
}
]
}
```
@@ -1,30 +0,0 @@
info:
name: Get Dashboard Sections by Library
type: http
seq: 3
http:
method: GET
url: '{{base_url}}/api/dashboard/sections'
auth: inherit
runtime:
scripts:
- type: tests
code: "test(\"status must be 200\", function() {\n expect(res.status).to.eql(200);"
docs: |-
Get all dashboard sections for a specific library.
**Endpoint**: GET /api/dashboard/sections
**Auth**: Required (Bearer token)
## Query Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| library_id | string | Yes | Library UUID |
## Response
Returns array of sections including:
- Smart sections (continue-reading, in-progress, recently-added, etc.)
- User collections marked for dashboard
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Dashboard Preferences
type: http
seq: 1
http:
method: GET
url: '{{base_url}}/api/dashboard/preferences'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Dashboard Preferences
`GET /api/dashboard/preferences` — auth: jwt.
Canonical reference: `docs/developer/api/dashboard/get_preferences.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Get Dashboard Sections
type: http
seq: 2
http:
method: GET
url: '{{base_url}}/api/dashboard/sections'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Get Dashboard Sections
`GET /api/dashboard/sections` — auth: jwt.
Canonical reference: `docs/developer/api/dashboard/get_sections.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -1,16 +0,0 @@
info:
name: Restore System Collection
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/dashboard/restore-system-collection'
auth: inherit
body:
type: json
data: "{\n \"collection_name\": \"continue-reading\"\n}"
runtime:
scripts:
- type: tests
code: "test(\"status must be 200 with valid collection\", function() {\n expect(res.status).to.eql(200);\n});\n\ntest(\"response has success message\", function() {\n expect(res.body.message).to.exist;\n});"
docs: "Restore a system collection to its default state.\n\n**Endpoint**: POST /api/dashboard/restore-system-collection\n**Auth**: Required (Bearer token)\n\n## Request Body\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| collection_name | string | Yes | Name of system collection to restore |\n\nValid collection names:\n- `continue-reading`: Books you're currently reading (0 < progress < 1)\n- `recently-added`: Newly added items to this library\n- `recently-read`: Books you've finished (progress >= 1)\n- `not-started`: Books you haven't read yet (progress = 0 or no record)\n\n## Example Request\n\n```json\n{\n \"collection_name\": \"continue-reading\"\n}\n```\n\n## Response\n\nReturns success message on restore."
@@ -0,0 +1,28 @@
info:
name: Restore System Collection
type: http
seq: 3
http:
method: POST
url: '{{base_url}}/api/dashboard/restore-system-collection'
auth: inherit
body:
type: json
data: |-
{
"collection_name": "Continue Reading",
"reset_type": "full"
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Restore System Collection
`POST /api/dashboard/restore-system-collection` — auth: jwt.
Canonical reference: `docs/developer/api/dashboard/restore_system_collection.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -1,17 +0,0 @@
info:
name: Update Collection Dashboard Visibility
type: http
seq: 5
http:
method: PUT
url: '{{base_url}}/api/collections/{{collection_id}}'
auth: inherit
runtime:
scripts:
- type: tests
code: "test(\"updates collection successfully\", function() {\n expect(res.status).to.eql(200);"
docs: |-
Update collection settings including dashboard visibility.
**Endpoint**: PUT /api/collections/{id
-16
View File
@@ -1,16 +0,0 @@
info:
name: Update Dashboard Preferences
type: http
seq: 2
http:
method: PUT
url: '{{base_url}}/api/dashboard/preferences'
auth: inherit
body:
type: json
data: "{\n \"library_id\": \"{{library_id}}\",\n \"hidden_collections\": [\"not-started\"],\n \"collection_order\": [\"recently-added\", \"continue-reading\", \"recently-read\"],\n \"items_per_section\": 20\n}"
runtime:
scripts:
- type: tests
code: "test(\"status must be 200 with valid request\", function() {\n expect(res.status).to.eql(200);\n});"
docs: "Update dashboard preferences for the authenticated user.\n\n**Endpoint**: PUT /api/dashboard/preferences\n**Auth**: Required (Bearer token)\n\n## Request Body\n\n| Field | Type | Required | Description |\n|-------|------|----------|-------------|\n| library_id | string | Yes | Library UUID |\n| hidden_collections | array | No | Collection names to hide from dashboard |\n| collection_order | array | No | Collection names in custom display order |\n| items_per_section | int | No | Items to show per section (default: 20) |\n\n## Example Request\n\n```json\n{\n \"library_id\": \"cc23c3a7-f8fb-451a-a78d-2a16df1b725a\",\n \"hidden_collections\": [\"not-started\"],\n \"collection_order\": [\"recently-added\", \"continue-reading\", \"recently-read\"],\n \"items_per_section\": 20\n}\n```\n\n## Response\n\nReturns updated preferences object on success."
+30
View File
@@ -0,0 +1,30 @@
info:
name: Update Dashboard Preferences
type: http
seq: 4
http:
method: PUT
url: '{{base_url}}/api/dashboard/preferences'
auth: inherit
body:
type: json
data: |-
{
"library_id": "550e8400-e29b-41d4-a716-446655440000",
"hidden_collections": ["not-started"],
"collection_order": ["recently-added", "continue-reading", "recently-read"],
"items_per_section": 20
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Update Dashboard Preferences
`PUT /api/dashboard/preferences` — auth: jwt.
Canonical reference: `docs/developer/api/dashboard/update_preferences.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+29
View File
@@ -0,0 +1,29 @@
info:
name: Add to Shelf
type: http
seq: 1
http:
method: POST
url: '{{base_url}}/api/devices/{{device_id}}/shelves'
auth: inherit
body:
type: json
data: |-
{
"media_item_ids": ["550e8400-e29b-41d4-a716-446655440001"],
"shelf_name": "Default",
"shelf_position": 0
}
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Add to Shelf
`POST /api/devices/:id/shelves` — auth: jwt.
Canonical reference: `docs/developer/api/devices/add_to_shelf.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
@@ -0,0 +1,21 @@
info:
name: Approve Device Registration
type: http
seq: 2
http:
method: GET
url: '{{base_url}}/api/devices/approve/{{registration_id}}'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Approve Device Registration
`GET /api/devices/approve/:registration_id` — auth: jwt.
Canonical reference: `docs/developer/api/devices/approve_device_registration.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
+21
View File
@@ -0,0 +1,21 @@
info:
name: Clear Shelf
type: http
seq: 3
http:
method: DELETE
url: '{{base_url}}/api/devices/{{device_id}}/shelves/clear'
auth: inherit
settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5
docs: |-
# Clear Shelf
`DELETE /api/devices/:id/shelves/clear` — auth: jwt.
Canonical reference: `docs/developer/api/devices/clear_shelf.md` (docs site → Developer → API). This request is GENERATED from that doc; edit the doc, not this file.
Loaded 100 of 353 files, more files were not shown because too many files have changed in this diff. Show more