# API Contract: Upload Thumbnails **Branch**: `003-upload-thumbnails` | **Date**: 2026-05-03 --- ## New endpoint ### `GET /api/v1/images/{image_id}/thumbnail` Returns the thumbnail content for the given image. If no thumbnail was generated (image pre-dates the feature or generation failed), falls back to the full-size original. **Path parameters** | Parameter | Type | Description | |-----------|------|-------------| | `image_id` | UUID | Unique identifier of the image | **Responses** #### `200 OK` — Thumbnail (or original fallback) content | Header | Value | Notes | |--------|-------|-------| | `Content-Type` | `image/webp` | Always WebP when a thumbnail exists; original `mime_type` when falling back to the original | | `ETag` | `"{sha256-hex}"` | Same hash as the original image — content is immutable | | `Cache-Control` | `public, max-age=31536000, immutable` | Safe: thumbnail content never changes | Body: raw image bytes (WebP thumbnail, or original bytes as fallback). #### `404 Not Found` — Image not found ```json { "detail": "Image not found", "code": "image_not_found" } ``` #### `500 Internal Server Error` — Storage retrieval failure ```json { "detail": "Failed to retrieve image content", "code": "storage_error" } ``` --- ## Changed endpoint: `POST /api/v1/images` The upload response body gains one new field: | Field | Type | Notes | |-------|------|-------| | `thumbnail_key` | `string \| null` | S3 key of the generated thumbnail. `null` if generation failed. | All existing fields are unchanged. **Example response** (new field only shown): ```json { "id": "...", "thumbnail_key": "abc123…-thumb", ... } ``` --- ## Changed endpoint: `GET /api/v1/images` and `GET /api/v1/images/{id}` Both metadata responses gain the same `thumbnail_key` field (`string | null`). --- ## UI contract The Angular `ImageService` gains one new method: ``` getThumbnailUrl(id: string): string → '/api/v1/images/{id}/thumbnail' ``` The `ImageRecord` interface gains: ``` thumbnail_key: string | null; ``` The library grid component uses `getThumbnailUrl(image.id)` as the `src` for every grid cell. The detail component continues using `getFileUrl(image.id)`.