openapi: 3.0.3 info: title: StoryCove API description: | StoryCove is a self-hosted web application for storing, organizing, and reading short stories from various internet sources. ## Features - Story management with HTML content support - Author profiles with ratings and metadata - Tag-based categorization and series organization - Full-text search powered by Typesense - Image upload for covers and avatars - JWT-based authentication ## Authentication All endpoints (except `/api/auth/login`) require JWT authentication via httpOnly cookies. version: 1.0.0 contact: name: StoryCove license: name: MIT servers: - url: http://localhost:6925/api description: Local development server - url: https://storycove.hardegger.io/api description: Production server security: - cookieAuth: [] paths: # Authentication endpoints /auth/login: post: tags: [Authentication] summary: Login with password description: Authenticate with application password and receive JWT token security: [] # No authentication required requestBody: required: true content: application/json: schema: type: object required: [password] properties: password: type: string description: Application password responses: '200': description: Authentication successful headers: Set-Cookie: description: JWT token cookie schema: type: string example: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...; HttpOnly; Max-Age=86400 content: application/json: schema: type: object properties: message: type: string example: Authentication successful token: type: string example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... '401': $ref: '#/components/responses/Unauthorized' /auth/logout: post: tags: [Authentication] summary: Logout and clear token description: Clear authentication token and logout responses: '200': description: Logout successful content: application/json: schema: $ref: '#/components/schemas/MessageResponse' /auth/verify: get: tags: [Authentication] summary: Verify token validity description: Check if current JWT token is valid responses: '200': description: Token is valid content: application/json: schema: $ref: '#/components/schemas/MessageResponse' '401': $ref: '#/components/responses/Unauthorized' # Story endpoints /stories: get: tags: [Stories] summary: List all stories description: Get paginated list of all stories parameters: - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/SizeParam' - $ref: '#/components/parameters/SortByParam' - $ref: '#/components/parameters/SortDirParam' responses: '200': description: Stories retrieved successfully content: application/json: schema: $ref: '#/components/schemas/PagedStories' '401': $ref: '#/components/responses/Unauthorized' post: tags: [Stories] summary: Create new story description: Create a new story with optional author, series, and tags requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateStoryRequest' responses: '201': description: Story created successfully content: application/json: schema: $ref: '#/components/schemas/StoryDto' '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' /stories/{id}: get: tags: [Stories] summary: Get story by ID description: Retrieve a specific story by its UUID parameters: - name: id in: path required: true schema: type: string format: uuid description: Story UUID responses: '200': description: Story retrieved successfully content: application/json: schema: $ref: '#/components/schemas/StoryDto' '404': $ref: '#/components/responses/NotFound' '401': $ref: '#/components/responses/Unauthorized' put: tags: [Stories] summary: Update story description: Update an existing story parameters: - name: id in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UpdateStoryRequest' responses: '200': description: Story updated successfully content: application/json: schema: $ref: '#/components/schemas/StoryDto' '400': $ref: '#/components/responses/BadRequest' '404': $ref: '#/components/responses/NotFound' '401': $ref: '#/components/responses/Unauthorized' delete: tags: [Stories] summary: Delete story description: Delete a story and all its relationships parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: Story deleted successfully content: application/json: schema: $ref: '#/components/schemas/MessageResponse' '404': $ref: '#/components/responses/NotFound' '401': $ref: '#/components/responses/Unauthorized' /stories/search: get: tags: [Stories] summary: Search stories description: Search stories using Typesense full-text search parameters: - name: query in: query required: true schema: type: string description: Search query - $ref: '#/components/parameters/PageParam' - $ref: '#/components/parameters/SizeParam' - name: authors in: query schema: type: array items: type: string description: Filter by author names - name: tags in: query schema: type: array items: type: string description: Filter by tag names - name: minRating in: query schema: type: integer minimum: 1 maximum: 5 - name: maxRating in: query schema: type: integer minimum: 1 maximum: 5 responses: '200': description: Search results content: application/json: schema: $ref: '#/components/schemas/SearchResult' '401': $ref: '#/components/responses/Unauthorized' components: securitySchemes: cookieAuth: type: apiKey in: cookie name: token parameters: PageParam: name: page in: query schema: type: integer minimum: 0 default: 0 description: Page number (0-based) SizeParam: name: size in: query schema: type: integer minimum: 1 maximum: 100 default: 20 description: Page size SortByParam: name: sortBy in: query schema: type: string default: createdAt description: Field to sort by SortDirParam: name: sortDir in: query schema: type: string enum: [asc, desc] default: desc description: Sort direction schemas: StoryDto: type: object properties: id: type: string format: uuid title: type: string summary: type: string nullable: true description: type: string nullable: true contentHtml: type: string nullable: true contentPlain: type: string nullable: true sourceUrl: type: string nullable: true coverPath: type: string nullable: true wordCount: type: integer rating: type: integer minimum: 1 maximum: 5 nullable: true volume: type: integer nullable: true authorId: type: string format: uuid nullable: true authorName: type: string nullable: true seriesId: type: string format: uuid nullable: true seriesName: type: string nullable: true tags: type: array items: $ref: '#/components/schemas/TagDto' createdAt: type: string format: date-time updatedAt: type: string format: date-time CreateStoryRequest: type: object required: [title] properties: title: type: string maxLength: 255 summary: type: string description: type: string maxLength: 1000 contentHtml: type: string sourceUrl: type: string volume: type: integer authorId: type: string format: uuid authorName: type: string seriesId: type: string format: uuid seriesName: type: string tagNames: type: array items: type: string UpdateStoryRequest: type: object properties: title: type: string maxLength: 255 summary: type: string description: type: string maxLength: 1000 contentHtml: type: string sourceUrl: type: string volume: type: integer authorId: type: string format: uuid seriesId: type: string format: uuid tagNames: type: array items: type: string TagDto: type: object properties: id: type: string format: uuid name: type: string storyCount: type: integer createdAt: type: string format: date-time updatedAt: type: string format: date-time PagedStories: type: object properties: content: type: array items: $ref: '#/components/schemas/StoryDto' pageable: $ref: '#/components/schemas/Pageable' totalElements: type: integer totalPages: type: integer last: type: boolean first: type: boolean numberOfElements: type: integer size: type: integer number: type: integer empty: type: boolean Pageable: type: object properties: sort: $ref: '#/components/schemas/Sort' pageNumber: type: integer pageSize: type: integer offset: type: integer paged: type: boolean unpaged: type: boolean Sort: type: object properties: sorted: type: boolean unsorted: type: boolean SearchResult: type: object properties: results: type: array items: $ref: '#/components/schemas/StoryDto' totalHits: type: integer format: int64 page: type: integer perPage: type: integer query: type: string searchTimeMs: type: integer format: int64 MessageResponse: type: object properties: message: type: string ErrorResponse: type: object properties: error: type: string details: type: object additionalProperties: type: string responses: BadRequest: description: Bad request - validation error content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' Unauthorized: description: Authentication required or invalid token content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' tags: - name: Authentication description: Authentication endpoints - name: Stories description: Story management endpoints - name: Authors description: Author management endpoints - name: Tags description: Tag management endpoints - name: Series description: Series management endpoints - name: Files description: File upload and management endpoints - name: Search description: Search and indexing endpoints - name: Configuration description: Application configuration endpoints