21 KiB
StoryCove REST API Documentation
Overview
StoryCove provides a comprehensive REST API for managing stories, authors, tags, and series. All endpoints use JSON for request and response data, follow RESTful conventions, and require JWT authentication (except for login).
Base URL: /api
Authentication: JWT tokens via httpOnly cookies
Content-Type: application/json (unless otherwise specified)
Authentication
All API endpoints (except authentication endpoints) require JWT authentication. Tokens are provided via httpOnly cookies and expire after 24 hours.
Authentication Flow
- Login:
POST /api/auth/loginwith password - Receive Token: Server sets httpOnly cookie with JWT
- Access Protected Endpoints: Cookie automatically sent with requests
- Token Expiry: 24 hours, requires re-authentication
Authentication Endpoints
POST /api/auth/login
Description: Authenticate with application password and receive JWT token.
Request Body:
{
"password": "string" // Required, application password
}
Response (200 OK):
{
"message": "Authentication successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Response (401 Unauthorized):
{
"error": "Invalid password"
}
Side Effects: Sets httpOnly cookie token with 24-hour expiration.
POST /api/auth/logout
Description: Clear authentication token and logout.
Request Body: None
Response (200 OK):
{
"message": "Logged out successfully"
}
Side Effects: Clears the token cookie.
GET /api/auth/verify
Description: Verify if current JWT token is valid.
Authentication: Required
Response (200 OK):
{
"message": "Token is valid"
}
Response (401 Unauthorized):
{
"error": "Token is invalid or expired"
}
Story Management Endpoints
GET /api/stories
Description: Get paginated list of all stories.
Query Parameters:
page(integer, default: 0): Page number (0-based)size(integer, default: 20): Page size (max: 100)sortBy(string, default: "createdAt"): Sort fieldsortDir(string, default: "desc"): Sort direction (asc/desc)
Response (200 OK):
{
"content": [StoryDto],
"pageable": {
"sort": { "sorted": true, "unsorted": false },
"pageNumber": 0,
"pageSize": 20,
"offset": 0,
"paged": true,
"unpaged": false
},
"totalElements": 150,
"totalPages": 8,
"last": false,
"first": true,
"numberOfElements": 20,
"size": 20,
"number": 0,
"sort": { "sorted": true, "unsorted": false },
"empty": false
}
GET /api/stories/{id}
Description: Get specific story by UUID.
Path Parameters:
id(UUID): Story identifier
Response (200 OK): StoryDto
Response (404 Not Found):
{
"error": "Story not found with id: {id}"
}
POST /api/stories
Description: Create a new story.
Request Body:
{
"title": "string", // Required
"summary": "string", // Optional
"description": "string", // Optional
"contentHtml": "<p>Story content</p>", // Optional
"sourceUrl": "https://example.com", // Optional
"volume": 1, // Optional, integer
"authorId": "uuid", // Optional, use either authorId or authorName
"authorName": "Author Name", // Optional, creates author if doesn't exist
"seriesId": "uuid", // Optional, use either seriesId or seriesName
"seriesName": "Series Name", // Optional, creates series if doesn't exist
"tagNames": ["tag1", "tag2"] // Optional, creates tags if they don't exist
}
Response (201 Created): StoryDto
Response (400 Bad Request):
{
"error": "Title is required",
"details": {
"title": "must not be blank"
}
}
PUT /api/stories/{id}
Description: Update existing story. All fields are optional.
Path Parameters:
id(UUID): Story identifier
Request Body: Same as POST, all fields optional
Response (200 OK): StoryDto
DELETE /api/stories/{id}
Description: Delete story and all associated relationships.
Path Parameters:
id(UUID): Story identifier
Response (200 OK):
{
"message": "Story deleted successfully"
}
POST /api/stories/{id}/cover
Description: Upload cover image for story.
Path Parameters:
id(UUID): Story identifier
Content-Type: multipart/form-data
Form Parameters:
file(File): Image file (JPG, PNG, WebP, max 5MB)
Response (200 OK):
{
"message": "Cover uploaded successfully",
"coverPath": "covers/story-uuid-cover.jpg",
"coverUrl": "/api/files/images/covers/story-uuid-cover.jpg"
}
DELETE /api/stories/{id}/cover
Description: Remove cover image from story.
Path Parameters:
id(UUID): Story identifier
Response (200 OK):
{
"message": "Cover removed successfully"
}
POST /api/stories/{id}/rating
Description: Set story rating.
Path Parameters:
id(UUID): Story identifier
Request Body:
{
"rating": 4 // Integer, 1-5
}
Response (200 OK): StoryDto
Tag Management for Stories
POST /api/stories/{id}/tags/{tagId}
Description: Add existing tag to story.
Path Parameters:
id(UUID): Story identifiertagId(UUID): Tag identifier
Response (200 OK): StoryDto
DELETE /api/stories/{id}/tags/{tagId}
Description: Remove tag from story.
Path Parameters:
id(UUID): Story identifiertagId(UUID): Tag identifier
Response (200 OK): StoryDto
Story Search and Filtering
GET /api/stories/search
Description: Search stories using Typesense full-text search.
Query Parameters:
query(string, required): Search querypage(integer, default: 0): Page numbersize(integer, default: 20): Page sizeauthors(string[]): Filter by author namestags(string[]): Filter by tag namesminRating(integer): Minimum rating filtermaxRating(integer): Maximum rating filtersortBy(string): Sort fieldsortDir(string): Sort direction
Response (200 OK):
{
"results": [StorySearchDto],
"totalHits": 42,
"page": 0,
"perPage": 20,
"query": "search terms",
"searchTimeMs": 15
}
GET /api/stories/search/suggestions
Description: Get search query suggestions.
Query Parameters:
query(string, required): Partial search querylimit(integer, default: 5): Number of suggestions
Response (200 OK):
["suggestion 1", "suggestion 2", "suggestion 3"]
Story Collections
GET /api/stories/author/{authorId}
Description: Get stories by specific author.
Path Parameters:
authorId(UUID): Author identifier
Query Parameters: Standard pagination parameters
Response (200 OK): Paginated StoryDto list
GET /api/stories/series/{seriesId}
Description: Get stories in series, ordered by volume.
Path Parameters:
seriesId(UUID): Series identifier
Response (200 OK): StoryDto[] (ordered by volume)
GET /api/stories/tags/{tagName}
Description: Get stories with specific tag.
Path Parameters:
tagName(string): Tag name
Query Parameters: Standard pagination parameters
Response (200 OK): Paginated StoryDto list
GET /api/stories/recent
Description: Get recently added stories.
Query Parameters:
limit(integer, default: 10): Number of stories
Response (200 OK): StoryDto[]
GET /api/stories/top-rated
Description: Get top-rated stories.
Query Parameters:
limit(integer, default: 10): Number of stories
Response (200 OK): StoryDto[]
Author Management Endpoints
GET /api/authors
Description: Get paginated list of all authors.
Query Parameters: Standard pagination and sorting parameters
Response (200 OK): Paginated AuthorDto list
GET /api/authors/{id}
Description: Get specific author by UUID.
Path Parameters:
id(UUID): Author identifier
Response (200 OK): AuthorDto
POST /api/authors
Description: Create new author.
Request Body:
{
"name": "Author Name", // Required, unique
"notes": "Notes about the author", // Optional
"urls": ["https://example.com"] // Optional, list of URLs
}
Response (201 Created): AuthorDto
PUT /api/authors/{id} (JSON)
Description: Update author with JSON data.
Path Parameters:
id(UUID): Author identifier
Content-Type: application/json
Request Body:
{
"name": "Updated Name", // Optional
"notes": "Updated notes", // Optional
"urls": ["url1", "url2"], // Optional, replaces all URLs
"rating": 4 // Optional, 1-5
}
Response (200 OK): AuthorDto
PUT /api/authors/{id} (Multipart)
Description: Update author with multipart form data (supports avatar upload).
Path Parameters:
id(UUID): Author identifier
Content-Type: multipart/form-data
Form Parameters:
name(string): Author namenotes(string): Author notesurls(string[]): List of URLsauthorRating(integer): Author ratingavatar(File): Avatar image file
Response (200 OK): AuthorDto
DELETE /api/authors/{id}
Description: Delete author and all associated data.
Path Parameters:
id(UUID): Author identifier
Response (200 OK):
{
"message": "Author deleted successfully"
}
Author Image Management
POST /api/authors/{id}/avatar
Description: Upload avatar image for author.
Path Parameters:
id(UUID): Author identifier
Content-Type: multipart/form-data
Form Parameters:
file(File): Avatar image (JPG, PNG, WebP, max 5MB)
Response (200 OK):
{
"message": "Avatar uploaded successfully",
"avatarPath": "avatars/author-uuid-avatar.jpg",
"avatarUrl": "/api/files/images/avatars/author-uuid-avatar.jpg"
}
DELETE /api/authors/{id}/avatar
Description: Remove avatar image from author.
Response (200 OK):
{
"message": "Avatar removed successfully"
}
Author URL Management
POST /api/authors/{id}/urls
Description: Add URL to author.
Path Parameters:
id(UUID): Author identifier
Request Body:
{
"url": "https://example.com"
}
Response (200 OK): AuthorDto
DELETE /api/authors/{id}/urls
Description: Remove URL from author.
Request Body:
{
"url": "https://example.com"
}
Response (200 OK): AuthorDto
Author Search and Collections
GET /api/authors/search
Description: Search authors by name (database search).
Query Parameters:
query(string, required): Search query- Standard pagination parameters
Response (200 OK): Paginated AuthorDto list
GET /api/authors/search-typesense
Description: Search authors using Typesense.
Query Parameters:
q(string, default: "*"): Search querypage,size: PaginationsortBy(string, default: "name"): Sort fieldsortOrder(string, default: "asc"): Sort order
Response (200 OK): SearchResultDto<AuthorDto>
GET /api/authors/top-rated
Description: Get top-rated authors.
Query Parameters:
limit(integer, default: 10): Number of authors
Response (200 OK): AuthorDto[]
Tag Management Endpoints
GET /api/tags
Description: Get paginated list of all tags.
Query Parameters: Standard pagination and sorting parameters
Response (200 OK): Paginated TagDto list
GET /api/tags/{id}
Description: Get specific tag by UUID.
Response (200 OK): TagDto
POST /api/tags
Description: Create new tag.
Request Body:
{
"name": "tag-name" // Required, max 100 characters, will be lowercased
}
Response (201 Created): TagDto
PUT /api/tags/{id}
Description: Update tag name.
Request Body:
{
"name": "new-tag-name" // Optional
}
Response (200 OK): TagDto
DELETE /api/tags/{id}
Description: Delete tag and remove from all stories.
Response (200 OK):
{
"message": "Tag deleted successfully"
}
Tag Collections and Search
GET /api/tags/search
Description: Search tags by name.
Query Parameters:
query(string, required): Search query- Standard pagination parameters
Response (200 OK): Paginated TagDto list
GET /api/tags/autocomplete
Description: Get tag suggestions for autocomplete.
Query Parameters:
query(string, required): Partial tag namelimit(integer, default: 10): Number of suggestions
Response (200 OK): TagDto[]
GET /api/tags/popular
Description: Get most used tags.
Query Parameters:
limit(integer, default: 20): Number of tags
Response (200 OK): TagDto[] (ordered by usage count)
GET /api/tags/unused
Description: Get tags not used by any stories.
Response (200 OK): TagDto[]
GET /api/tags/stats
Description: Get tag usage statistics.
Response (200 OK):
{
"totalTags": 150,
"usedTags": 120,
"unusedTags": 30
}
Series Management Endpoints
GET /api/series
Description: Get paginated list of all series.
Response (200 OK): Paginated SeriesDto list
GET /api/series/{id}
Description: Get specific series by UUID.
Response (200 OK): SeriesDto
POST /api/series
Description: Create new series.
Request Body:
{
"name": "Series Name", // Required, max 255 characters
"description": "Description" // Optional, max 1000 characters
}
Response (201 Created): SeriesDto
PUT /api/series/{id}
Description: Update series.
Request Body:
{
"name": "Updated Name", // Optional
"description": "Updated desc" // Optional
}
Response (200 OK): SeriesDto
DELETE /api/series/{id}
Description: Delete series and remove series association from stories.
Response (200 OK):
{
"message": "Series deleted successfully"
}
Series Collections
GET /api/series/search
Description: Search series by name.
Query Parameters:
query(string, required): Search query- Standard pagination parameters
Response (200 OK): Paginated SeriesDto list
GET /api/series/with-stories
Description: Get series that have stories.
Query Parameters:
limit(integer, default: 20): Number of series
Response (200 OK): SeriesDto[]
GET /api/series/popular
Description: Get most popular series (by story count).
Query Parameters:
limit(integer, default: 10): Number of series
Response (200 OK): SeriesDto[]
GET /api/series/empty
Description: Get series with no stories.
Response (200 OK): SeriesDto[]
GET /api/series/stats
Description: Get series statistics.
Response (200 OK):
{
"totalSeries": 50,
"seriesWithStories": 35,
"emptySeries": 15
}
File Management Endpoints
POST /api/files/upload/cover
Description: Upload cover image (generic endpoint).
Content-Type: multipart/form-data
Form Parameters:
file(File): Image file
Response (200 OK):
{
"message": "Cover uploaded successfully",
"path": "covers/filename.jpg",
"url": "/api/files/images/covers/filename.jpg"
}
POST /api/files/upload/avatar
Description: Upload avatar image (generic endpoint).
Response (200 OK):
{
"message": "Avatar uploaded successfully",
"path": "avatars/filename.jpg",
"url": "/api/files/images/avatars/filename.jpg"
}
GET /api/files/images/**
Description: Serve image files.
Query Parameters:
path(string, required): Image path
Response: Image file with appropriate Content-Type
Headers:
Cache-Control: max-age=31536000(1 year)Content-Type: image/jpeg|png|webp
DELETE /api/files/images
Description: Delete image file.
Query Parameters:
path(string, required): Image path to delete
Response (200 OK):
{
"message": "Image deleted successfully"
}
Search and Indexing Endpoints
POST /api/search/reindex
Description: Manually reindex all stories in Typesense.
Response (200 OK):
{
"message": "Successfully reindexed all stories",
"storiesCount": 1250
}
GET /api/search/health
Description: Check search service health status.
Response (200 OK):
{
"status": "healthy",
"message": "Typesense is running and responsive"
}
Response (503 Service Unavailable):
{
"status": "unhealthy",
"message": "Typesense is not responding",
"error": "Connection refused"
}
Configuration Endpoints
GET /api/config/html-sanitization
Description: Get HTML sanitization configuration for frontend.
Response (200 OK):
{
"allowedTags": [
"p", "br", "div", "span", "h1", "h2", "h3",
"b", "strong", "i", "em", "u", "s", "del"
],
"allowedAttributes": {
"p": ["class", "style"],
"div": ["class", "style"],
"span": ["class", "style"]
},
"allowedCssProperties": [
"color", "background-color", "font-size",
"font-weight", "font-style", "text-align"
],
"removedAttributes": {
"a": ["href", "target"]
},
"description": "HTML sanitization configuration for StoryCove"
}
Data Transfer Objects (DTOs)
StoryDto
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"title": "Story Title",
"summary": "Story summary",
"description": "Story description",
"contentHtml": "<p>HTML content</p>",
"contentPlain": "Plain text content",
"sourceUrl": "https://example.com/story",
"coverPath": "covers/story-cover.jpg",
"wordCount": 1500,
"rating": 4,
"volume": 1,
"authorId": "author-uuid",
"authorName": "Author Name",
"seriesId": "series-uuid",
"seriesName": "Series Name",
"tags": [
{
"id": "tag-uuid",
"name": "tag-name",
"storyCount": 10,
"createdAt": "2024-01-15T10:30:00",
"updatedAt": "2024-01-15T10:30:00"
}
],
"createdAt": "2024-01-15T10:30:00",
"updatedAt": "2024-01-16T14:20:00"
}
AuthorDto
{
"id": "author-uuid",
"name": "Author Name",
"notes": "Notes about the author",
"avatarImagePath": "avatars/author-avatar.jpg",
"authorRating": 4,
"averageStoryRating": 3.8,
"urls": [
"https://authorwebsite.com",
"https://twitter.com/author"
],
"storyCount": 15,
"createdAt": "2024-01-10T09:00:00",
"updatedAt": "2024-01-16T11:00:00"
}
TagDto
{
"id": "tag-uuid",
"name": "fantasy",
"storyCount": 25,
"createdAt": "2024-01-05T12:00:00",
"updatedAt": "2024-01-05T12:00:00"
}
SeriesDto
{
"id": "series-uuid",
"name": "Series Name",
"description": "Series description",
"storyCount": 5,
"createdAt": "2024-01-01T00:00:00"
}
SearchResultDto
{
"results": [T], // Array of result objects
"totalHits": 42,
"page": 0,
"perPage": 20,
"query": "search terms",
"searchTimeMs": 15
}
Error Handling
Standard HTTP Status Codes
- 200 OK: Request successful
- 201 Created: Resource created successfully
- 400 Bad Request: Invalid request data or validation error
- 401 Unauthorized: Authentication required or invalid token
- 403 Forbidden: Access denied
- 404 Not Found: Resource not found
- 409 Conflict: Resource conflict (e.g., duplicate name)
- 413 Payload Too Large: File upload too large
- 415 Unsupported Media Type: Invalid file type
- 422 Unprocessable Entity: Validation error
- 500 Internal Server Error: Server error
Error Response Format
Single Error:
{
"error": "Error message"
}
Validation Errors:
{
"error": "Validation failed",
"details": {
"title": "must not be blank",
"rating": "must be between 1 and 5"
}
}
Field-Specific Error:
{
"error": "Author not found with id: 123e4567-e89b-12d3-a456-426614174000"
}
Rate Limiting and Performance
Request Limits
- No explicit rate limiting implemented
- File uploads limited to 5MB per file
- Page size limited to 100 items maximum
- Search queries have reasonable timeout limits
Performance Considerations
- Pagination used for all list endpoints
- Lazy loading for entity relationships
- Image files cached with 1-year expiration
- Search powered by Typesense for fast full-text search
- Database queries optimized with proper indexing
Best Practices
- Use pagination for large datasets
- Cache images and static content
- Use search endpoints for finding content rather than filtering
- Batch operations when possible
- Include only necessary fields in responses
This API documentation provides comprehensive coverage of all StoryCove endpoints with examples, error handling, and best practices for integration.