# API Contract: Image Content Endpoint **Branch**: `002-api-image-proxy` | **Date**: 2026-05-03 ## Changed endpoint ### `GET /api/v1/images/{image_id}/file` Serves the binary content of the image with the given ID, proxied through the API from object storage. **Path parameters** | Parameter | Type | Description | |-----------|------|-------------| | `image_id` | UUID | Unique identifier of the image | **Responses** #### `200 OK` — Image content | Header | Value | Notes | |--------|-------|-------| | `Content-Type` | `image/jpeg` \| `image/png` \| `image/gif` \| `image/webp` | Taken from stored `mime_type` | | `ETag` | `""` | SHA-256 hash of the image content; quoted string per RFC 7232 | | `Cache-Control` | `public, max-age=31536000, immutable` | Safe: images are immutable after upload | Body: raw image bytes. #### `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" } ``` No storage-specific details (bucket name, hostname, credentials) are exposed in error responses. --- ## Breaking change from prior behaviour | Aspect | Before | After | |--------|--------|-------| | Status code | `302 Found` | `200 OK` | | `Location` header | Present (presigned S3 URL) | Absent | | Body | Empty | Raw image bytes | | Browser behaviour | Follows redirect to storage | Receives content from API | The endpoint path is unchanged. The UI requires no URL-construction changes. --- ## Removed StorageBackend capability The `get_presigned_url` operation is removed from the `StorageBackend` interface. It was the only mechanism for generating time-limited direct-access URLs to stored objects, and had a single call site (`serve_image_file`). With the proxy in place it has no remaining callers. Any future need for presigned URLs (e.g., direct-upload flows) would require a new spec and a new interface method.