Replace the presigned-URL redirect (302) in GET /api/v1/images/{id}/file
with a direct proxy that fetches bytes from S3 server-side and returns them
to the client. The browser never contacts the storage backend, eliminating
the /etc/hosts workaround needed in local development.
- StorageBackend: swap get_presigned_url for get(key) -> bytes
- S3StorageBackend: implement get() via aiobotocore get_object
- serve_image_file: return Response with ETag + Cache-Control: immutable
- test_serving: assert 200 + content-type + ETag; add no-storage-details test
- Spec Kit artifacts for feature 002-api-image-proxy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2.0 KiB
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 |
"<sha256-hex>" |
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
{ "detail": "Image not found", "code": "image_not_found" }
500 Internal Server Error — Storage retrieval failure
{ "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.