openapi: 3.0.3 info: title: Webcomic API description: API for accessing webcomic data version: 1.0.0 contact: url: http://127.0.0.1:3000 servers: - url: http://127.0.0.1:3000 description: Development server - url: https://your-production-domain.com description: Production server (update with your actual domain) paths: /api/comics: get: summary: Get all comics description: | Returns all comics with enriched metadata. Supports optional pagination and section grouping. **Without pagination parameters:** Returns a simple array of all comics (newest first when using pagination, original order otherwise). **With pagination parameters:** Returns paginated response with section grouping (if enabled globally or via `group_by_section` parameter). operationId: getAllComics tags: - Comics parameters: - name: page in: query description: Page number for pagination (1-indexed). When provided, triggers paginated response format. required: false schema: type: integer minimum: 1 default: 1 example: 1 - name: per_page in: query description: Number of comics per page (max 100). When provided, triggers paginated response format. required: false schema: type: integer minimum: 1 maximum: 100 default: 24 example: 24 - name: group_by_section in: query description: Force section grouping in response (even when SECTIONS_ENABLED is false). When true, triggers paginated response format. required: false schema: type: boolean default: false example: false responses: '200': description: Successful response content: application/json: schema: oneOf: - type: array description: Simple array response (when no pagination parameters provided) items: $ref: '#/components/schemas/Comic' - $ref: '#/components/schemas/PaginatedComicsResponse' examples: simpleArray: summary: Simple array response (default) value: - number: 1 title: "First Comic" filename: "comic-001.jpg" mobile_filename: "comic-001-mobile.jpg" date: "2025-01-01" alt_text: "The very first comic" author_note: "This is where your comic journey begins!" full_width: true plain: true formatted_date: "Wednesday, January 1, 2025" author_note_is_html: false - number: 2 filename: "comic-002.jpg" date: "2025-01-08" alt_text: "The second comic" full_width: true plain: true formatted_date: "Wednesday, January 8, 2025" author_note_is_html: false paginatedResponse: summary: Paginated response (when using page/per_page parameters) value: sections: - section_title: "Chapter 1" comics: - number: 2 filename: "comic-002.jpg" date: "2025-01-08" alt_text: "The second comic" full_width: true plain: true formatted_date: "Wednesday, January 8, 2025" author_note_is_html: false - section_title: null comics: - number: 1 title: "First Comic" filename: "comic-001.jpg" date: "2025-01-01" alt_text: "The very first comic" full_width: true plain: true formatted_date: "Wednesday, January 1, 2025" author_note_is_html: false page: 1 per_page: 24 total_comics: 2 has_more: false /api/comics/{comic_id}: get: summary: Get a specific comic description: Returns a single comic by its number/ID with enriched metadata operationId: getComicById tags: - Comics parameters: - name: comic_id in: path description: Comic number/ID required: true schema: type: integer minimum: 1 example: 1 responses: '200': description: Successful response with comic data content: application/json: schema: $ref: '#/components/schemas/Comic' example: number: 1 title: "First Comic" filename: "comic-001.jpg" mobile_filename: "comic-001-mobile.jpg" date: "2025-01-01" alt_text: "The very first comic" author_note: "This is where your comic journey begins!" full_width: true plain: true formatted_date: "Wednesday, January 1, 2025" author_note_is_html: false '404': description: Comic not found content: application/json: schema: $ref: '#/components/schemas/Error' example: error: "Comic not found" components: schemas: Comic: type: object required: - number - filename - date - alt_text - full_width - plain - formatted_date - author_note_is_html - filenames - alt_texts - is_multi_image properties: number: type: integer description: Sequential comic number (unique identifier) minimum: 1 example: 1 title: type: string description: Comic title (optional, defaults to "#X" if not provided) example: "First Comic" filename: oneOf: - type: string description: Single image filename in static/images/comics/ - type: array description: Multiple image filenames for webtoon-style comics items: type: string example: "comic-001.jpg" filenames: type: array description: Normalized array of image filenames (computed field, always an array even for single images) items: type: string example: ["comic-001.jpg"] mobile_filename: type: string description: Optional mobile version of the comic image example: "comic-001-mobile.jpg" date: type: string format: date description: Publication date in YYYY-MM-DD format example: "2025-01-01" alt_text: oneOf: - type: string description: Accessibility text for single image or shared text for all images - type: array description: Individual accessibility text for each image in multi-image comics items: type: string example: "The very first comic" alt_texts: type: array description: Normalized array of alt texts matching filenames (computed field) items: type: string example: ["The very first comic"] is_multi_image: type: boolean description: Indicates if this is a multi-image comic (computed field) example: false author_note: type: string description: Author's note about the comic (plain text or HTML from markdown) example: "This is where your comic journey begins!" author_note_md: type: string description: Filename or path to markdown file for author note example: "2025-01-01.md" section: type: string description: Section/chapter title (appears on archive page when SECTIONS_ENABLED is true) example: "Chapter 1: Origins" full_width: type: boolean description: Whether the comic should display in full-width mode (computed from global default and per-comic override) example: true plain: type: boolean description: Whether the comic should display in plain mode without header/borders (computed from global default and per-comic override) example: true formatted_date: type: string description: Human-readable formatted date (computed field) example: "Wednesday, January 1, 2025" author_note_is_html: type: boolean description: Indicates whether author_note contains HTML (from markdown) or plain text (computed field) example: false ComicSection: type: object required: - section_title - comics properties: section_title: type: string nullable: true description: Section/chapter title (null for comics without a section) example: "Chapter 1" comics: type: array description: Comics in this section items: $ref: '#/components/schemas/Comic' PaginatedComicsResponse: type: object required: - sections - page - per_page - total_comics - has_more properties: sections: type: array description: Comics grouped by section items: $ref: '#/components/schemas/ComicSection' page: type: integer description: Current page number minimum: 1 example: 1 per_page: type: integer description: Number of comics per page minimum: 1 maximum: 100 example: 24 total_comics: type: integer description: Total number of comics across all pages minimum: 0 example: 100 has_more: type: boolean description: Whether there are more pages available example: true Error: type: object required: - error properties: error: type: string description: Error message example: "Comic not found" tags: - name: Comics description: Operations for accessing comic data