Short IDs become the canonical identifier in URLs (/i/:short_id), MinIO/R2 storage keys, and all API responses. Hash-based deduplication is preserved. Includes two-phase Alembic migration (003 adds nullable column, 004 enforces NOT NULL) with a backfill script to copy storage objects and populate short_id for existing images. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.3 KiB
3.3 KiB
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.
{
"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: equalsshort_id(post-migration)thumbnail_key: equals{short_id}-thumbornullif no thumbnail existsfile_url:{cdn_base}/{short_id}when CDN is configured;/api/v1/images/{short_id}/fileotherwisethumbnail_url:{cdn_base}/{short_id}-thumbornull
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_idpopulated
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)
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;
}