openapi: 3.0.3 info: title: Reference Board Viewer API description: | REST API for the Reference Board Viewer application - a web-based tool for artists to collect, organize, and manipulate visual reference images. version: 1.0.0 contact: name: API Support servers: - url: http://localhost:8000/api/v1 description: Development server - url: https://webref.example.com/api/v1 description: Production server tags: - name: Auth description: Authentication and user management - name: Boards description: Board operations - name: Images description: Image upload and management - name: Canvas description: Canvas operations (positioning, transformations) - name: Groups description: Image grouping - name: Sharing description: Board sharing security: - BearerAuth: [] paths: # ==================== Authentication ==================== /auth/register: post: tags: [Auth] summary: Register new user security: [] requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: type: string format: email example: user@example.com password: type: string minLength: 8 example: SecurePass123 responses: '201': description: User registered successfully content: application/json: schema: $ref: '#/components/schemas/UserResponse' '400': $ref: '#/components/responses/BadRequest' '409': $ref: '#/components/responses/Conflict' /auth/login: post: tags: [Auth] summary: Login user security: [] requestBody: required: true content: application/json: schema: type: object required: [email, password] properties: email: type: string format: email password: type: string responses: '200': description: Login successful content: application/json: schema: type: object properties: access_token: type: string example: eyJhbGciOiJIUzI1NiIs... token_type: type: string example: bearer user: $ref: '#/components/schemas/UserResponse' '401': $ref: '#/components/responses/Unauthorized' /auth/me: get: tags: [Auth] summary: Get current user responses: '200': description: Current user details content: application/json: schema: $ref: '#/components/schemas/UserResponse' '401': $ref: '#/components/responses/Unauthorized' # ==================== Boards ==================== /boards: get: tags: [Boards] summary: List user's boards parameters: - name: limit in: query schema: type: integer default: 50 maximum: 100 - name: offset in: query schema: type: integer default: 0 responses: '200': description: List of boards content: application/json: schema: type: object properties: boards: type: array items: $ref: '#/components/schemas/BoardSummary' total: type: integer limit: type: integer offset: type: integer post: tags: [Boards] summary: Create new board requestBody: required: true content: application/json: schema: type: object required: [title] properties: title: type: string minLength: 1 maxLength: 255 example: Character Design References description: type: string example: References for fantasy knight character responses: '201': description: Board created content: application/json: schema: $ref: '#/components/schemas/BoardDetail' '400': $ref: '#/components/responses/BadRequest' /boards/{board_id}: parameters: - $ref: '#/components/parameters/BoardId' get: tags: [Boards] summary: Get board details responses: '200': description: Board details with all images content: application/json: schema: $ref: '#/components/schemas/BoardDetail' '404': $ref: '#/components/responses/NotFound' patch: tags: [Boards] summary: Update board requestBody: content: application/json: schema: type: object properties: title: type: string description: type: string viewport_state: $ref: '#/components/schemas/ViewportState' responses: '200': description: Board updated content: application/json: schema: $ref: '#/components/schemas/BoardDetail' '404': $ref: '#/components/responses/NotFound' delete: tags: [Boards] summary: Delete board responses: '204': description: Board deleted '404': $ref: '#/components/responses/NotFound' # ==================== Images ==================== /boards/{board_id}/images: parameters: - $ref: '#/components/parameters/BoardId' post: tags: [Images] summary: Upload image(s) to board requestBody: required: true content: multipart/form-data: schema: type: object required: [files] properties: files: type: array items: type: string format: binary maxItems: 50 position: type: string description: JSON string of default position example: '{"x": 0, "y": 0}' responses: '201': description: Images uploaded content: application/json: schema: type: object properties: images: type: array items: $ref: '#/components/schemas/BoardImage' '400': $ref: '#/components/responses/BadRequest' '413': description: File too large content: application/json: schema: $ref: '#/components/schemas/Error' /boards/{board_id}/images/{image_id}: parameters: - $ref: '#/components/parameters/BoardId' - $ref: '#/components/parameters/ImageId' patch: tags: [Canvas] summary: Update image position/transformations requestBody: content: application/json: schema: type: object properties: position: $ref: '#/components/schemas/Position' transformations: $ref: '#/components/schemas/Transformations' z_order: type: integer group_id: type: string format: uuid nullable: true responses: '200': description: Image updated content: application/json: schema: $ref: '#/components/schemas/BoardImage' '404': $ref: '#/components/responses/NotFound' delete: tags: [Canvas] summary: Remove image from board responses: '204': description: Image removed from board '404': $ref: '#/components/responses/NotFound' /boards/{board_id}/images/bulk: parameters: - $ref: '#/components/parameters/BoardId' patch: tags: [Canvas] summary: Bulk update multiple images requestBody: required: true content: application/json: schema: type: object required: [image_ids, updates] properties: image_ids: type: array items: type: string format: uuid updates: type: object properties: position_delta: type: object properties: dx: type: number dy: type: number transformations: $ref: '#/components/schemas/Transformations' z_order_delta: type: integer responses: '200': description: Images updated content: application/json: schema: type: object properties: updated_count: type: integer '400': $ref: '#/components/responses/BadRequest' # ==================== Groups ==================== /boards/{board_id}/groups: parameters: - $ref: '#/components/parameters/BoardId' get: tags: [Groups] summary: List board groups responses: '200': description: List of groups content: application/json: schema: type: array items: $ref: '#/components/schemas/Group' post: tags: [Groups] summary: Create group requestBody: required: true content: application/json: schema: type: object required: [name, color, image_ids] properties: name: type: string example: Armor References color: type: string pattern: '^#[0-9A-Fa-f]{6}$' example: '#FF5733' annotation: type: string example: Blue plate armor designs image_ids: type: array items: type: string format: uuid responses: '201': description: Group created content: application/json: schema: $ref: '#/components/schemas/Group' '400': $ref: '#/components/responses/BadRequest' /boards/{board_id}/groups/{group_id}: parameters: - $ref: '#/components/parameters/BoardId' - $ref: '#/components/parameters/GroupId' patch: tags: [Groups] summary: Update group requestBody: content: application/json: schema: type: object properties: name: type: string color: type: string annotation: type: string responses: '200': description: Group updated content: application/json: schema: $ref: '#/components/schemas/Group' delete: tags: [Groups] summary: Delete group (ungroups images) responses: '204': description: Group deleted '404': $ref: '#/components/responses/NotFound' # ==================== Sharing ==================== /boards/{board_id}/share-links: parameters: - $ref: '#/components/parameters/BoardId' get: tags: [Sharing] summary: List board share links responses: '200': description: List of share links content: application/json: schema: type: array items: $ref: '#/components/schemas/ShareLink' post: tags: [Sharing] summary: Create share link requestBody: required: true content: application/json: schema: type: object required: [permission_level] properties: permission_level: type: string enum: [view-only, view-comment] expires_at: type: string format: date-time nullable: true responses: '201': description: Share link created content: application/json: schema: $ref: '#/components/schemas/ShareLink' /boards/{board_id}/share-links/{link_id}: parameters: - $ref: '#/components/parameters/BoardId' - name: link_id in: path required: true schema: type: string format: uuid delete: tags: [Sharing] summary: Revoke share link responses: '204': description: Share link revoked '404': $ref: '#/components/responses/NotFound' /shared/{token}: parameters: - name: token in: path required: true schema: type: string get: tags: [Sharing] summary: Access shared board security: [] responses: '200': description: Shared board details content: application/json: schema: type: object properties: board: $ref: '#/components/schemas/BoardDetail' permission_level: type: string enum: [view-only, view-comment] '404': description: Invalid or expired token content: application/json: schema: $ref: '#/components/schemas/Error' # ==================== Export ==================== /boards/{board_id}/export: parameters: - $ref: '#/components/parameters/BoardId' post: tags: [Boards] summary: Export board requestBody: required: true content: application/json: schema: type: object required: [format] properties: format: type: string enum: [zip, composite] resolution: type: integer enum: [1, 2, 4] default: 1 description: Resolution multiplier (for composite) responses: '200': description: Export file content: application/zip: schema: type: string format: binary image/png: schema: type: string format: binary '400': $ref: '#/components/responses/BadRequest' # ==================== Image Library ==================== /library/images: get: tags: [Images] summary: List user's image library parameters: - name: search in: query schema: type: string - name: limit in: query schema: type: integer default: 50 - name: offset in: query schema: type: integer default: 0 responses: '200': description: Image library content: application/json: schema: type: object properties: images: type: array items: $ref: '#/components/schemas/ImageMetadata' total: type: integer # ==================== Components ==================== components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT parameters: BoardId: name: board_id in: path required: true schema: type: string format: uuid ImageId: name: image_id in: path required: true schema: type: string format: uuid GroupId: name: group_id in: path required: true schema: type: string format: uuid schemas: UserResponse: type: object properties: id: type: string format: uuid email: type: string format: email created_at: type: string format: date-time BoardSummary: type: object properties: id: type: string format: uuid title: type: string description: type: string nullable: true image_count: type: integer thumbnail_url: type: string nullable: true created_at: type: string format: date-time updated_at: type: string format: date-time BoardDetail: allOf: - $ref: '#/components/schemas/BoardSummary' - type: object properties: viewport_state: $ref: '#/components/schemas/ViewportState' images: type: array items: $ref: '#/components/schemas/BoardImage' groups: type: array items: $ref: '#/components/schemas/Group' ViewportState: type: object properties: x: type: number example: 0 y: type: number example: 0 zoom: type: number minimum: 0.1 maximum: 5.0 example: 1.0 rotation: type: number minimum: 0 maximum: 360 example: 0 ImageMetadata: type: object properties: id: type: string format: uuid filename: type: string file_size: type: integer mime_type: type: string width: type: integer height: type: integer thumbnail_urls: type: object properties: low: type: string medium: type: string high: type: string created_at: type: string format: date-time reference_count: type: integer BoardImage: allOf: - $ref: '#/components/schemas/ImageMetadata' - type: object properties: position: $ref: '#/components/schemas/Position' transformations: $ref: '#/components/schemas/Transformations' z_order: type: integer group_id: type: string format: uuid nullable: true Position: type: object properties: x: type: number y: type: number Transformations: type: object properties: scale: type: number minimum: 0.01 maximum: 10.0 default: 1.0 rotation: type: number minimum: 0 maximum: 360 default: 0 opacity: type: number minimum: 0.0 maximum: 1.0 default: 1.0 flipped_h: type: boolean default: false flipped_v: type: boolean default: false crop: type: object nullable: true properties: x: type: number y: type: number width: type: number height: type: number greyscale: type: boolean default: false Group: type: object properties: id: type: string format: uuid name: type: string color: type: string pattern: '^#[0-9A-Fa-f]{6}$' annotation: type: string nullable: true member_count: type: integer created_at: type: string format: date-time ShareLink: type: object properties: id: type: string format: uuid token: type: string permission_level: type: string enum: [view-only, view-comment] url: type: string example: https://webref.example.com/shared/abc123... created_at: type: string format: date-time expires_at: type: string format: date-time nullable: true access_count: type: integer is_revoked: type: boolean Error: type: object properties: error: type: object properties: message: type: string code: type: string details: type: object nullable: true responses: BadRequest: description: Bad request content: application/json: schema: $ref: '#/components/schemas/Error' Unauthorized: description: Unauthorized content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: Resource not found content: application/json: schema: $ref: '#/components/schemas/Error' Conflict: description: Resource conflict content: application/json: schema: $ref: '#/components/schemas/Error'