# Contract: Image API (Short ID Update) ## ImageRecord Response Schema All image endpoints return this shape. `short_id` is a new field; all other fields are unchanged. ```json { "id": "7343d164-80bb-473b-b239-717f2842ae4e", "short_id": "xK7mN2pQ", "hash": "163dec08460650439f1e7439721e8e566aff7d8aaad60cf451e7d3518a334a23", "filename": "image.gif", "mime_type": "image/gif", "size_bytes": 1957149, "width": 265, "height": 199, "storage_key": "xK7mN2pQ", "thumbnail_key": "xK7mN2pQ-thumb", "file_url": "https://cdn.reactbin.juggalol.com/xK7mN2pQ", "thumbnail_url": "https://cdn.reactbin.juggalol.com/xK7mN2pQ-thumb", "created_at": "2026-05-09T02:46:29.520296+00:00", "tags": ["kfc"] } ``` **Constraints**: - `short_id`: exactly 8 alphanumeric characters `[a-zA-Z0-9]{8}` - `storage_key`: equals `short_id` (post-migration) - `thumbnail_key`: equals `{short_id}-thumb` or `null` if no thumbnail exists - `file_url`: `{cdn_base}/{short_id}` when CDN is configured; `/api/v1/images/{short_id}/file` otherwise - `thumbnail_url`: `{cdn_base}/{short_id}-thumb` or `null` --- ## Route Changes All routes that previously accepted `{image_id}` as a UUID now accept `{short_id}` as an 8-character alphanumeric string. ### GET /api/v1/images/{short_id} Fetch a single image by short ID. - **Path param**: `short_id` — 8-char alphanumeric string - **Response 200**: ImageRecord - **Response 404**: `{"detail": "Image not found", "code": "image_not_found"}` - **Response 422**: `{"detail": "Invalid image ID", "code": "invalid_short_id"}` if param is not 8 alphanumeric chars ### PATCH /api/v1/images/{short_id}/tags Update tags on an image. Auth required. - **Path param**: `short_id` — 8-char alphanumeric string - **Body**: `{"tags": ["tag1", "tag2"]}` - **Response 200**: ImageRecord (updated) - **Response 404/422**: same shape as above ### DELETE /api/v1/images/{short_id} Delete an image and its storage objects. Auth required. - **Path param**: `short_id` — 8-char alphanumeric string - **Response 204**: no body - **Response 404**: error envelope ### GET /api/v1/images/{short_id}/file Serve the raw image file (proxy mode, when CDN is not configured). - **Path param**: `short_id` - **Response 200**: raw image bytes with correct `Content-Type` ### GET /api/v1/images/{short_id}/thumbnail Serve the thumbnail (proxy mode). - **Path param**: `short_id` - **Response 200**: WebP bytes or original image if no thumbnail ### POST /api/v1/images (upload — unchanged route, updated response) - **Response**: ImageRecord with `short_id` populated --- ## Frontend Route Change | Old route | New route | |-----------------|--------------| | `/images/:id` | `/i/:id` | The `:id` segment now contains the `short_id` value (8 alphanumeric chars) rather than a UUID. --- ## ImageRecord TypeScript Interface (updated) ```typescript export interface ImageRecord { id: string; // UUID — retained, not used for routing short_id: string; // NEW — 8-char base62, used for all routing and API calls hash: string; filename: string; mime_type: string; size_bytes: number; width: number; height: number; storage_key: string; thumbnail_key: string | null; file_url: string; thumbnail_url: string | null; created_at: string; tags: string[]; duplicate?: boolean; } ```