API Reference · Assets & files
User Guide
Developer
Assets: artwork, content-addressed
Uploads are image-only and capped at 25 MB. Files are stored under their SHA-256 hash, so the same artwork never duplicates on disk, and fetched back byte-identical from /files/<name>.
/assetsJWT Bearer · multipart/form-dataUploads one image. The request must be multipart/form-data with a file field; the mime type must start with image/. Over the 25 MB cap the request fails with 413 before a byte is stored.
curl -X POST http://localhost/api/assets \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…" \
-F "file=@/path/to/artwork.png"{
"asset": {
"id": "cml4kc9gh0003jq8f2a1b0d9f",
"userId": "cml4k9x2b0000jq8f3h5a1d2e",
"filename": "9f2c4e6a8b0d1f3e5a7c9b8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f.png",
"originalName": "artwork.png",
"url": "/files/9f2c4e6a8b0d1f3e5a7c9b8d7e6f5a4b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f.png",
"mimeType": "image/png",
"sizeBytes": 184320,
"createdAt": "2026-08-06T10:26:40.000Z"
}
}| Field | Type | Description |
|---|---|---|
| 400 | ApiError | “A multipart file field is required.” |
| 413 | ApiError | File over the 25 MB artwork limit. |
| 415 | ApiError | “Only image uploads are allowed (received '<mime>').” |
| 401 | ApiError | Missing or expired JWT. |
/assetsJWT BearerLists your uploads, newest first.
curl http://localhost/api/assets \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"{ "assets": [ { "id": "cml4kc9gh0003jq8f2a1b0d9f", … } ], "total": 1 }/files/:nameNo authServes a stored upload by its stored filename (the <sha256>.<ext> pattern — path traversal is rejected by a strict name check). Responses carry immutable cache headers, and the bytes round-trip exactly. In the Docker deployment nginx serves /files/<name> straight from MinIO (the api stores objects with anonymous-read policy); in local development the api's local-disk driver serves the same path — same URLs, same behavior.
curl http://localhost/files/9f2c4e6a…c0d9e8f.png --output artwork.png
# → 200, image/png, byte-identical to the uploadWhere files live
Storage sits behind a StorageDriver interface chosen by the STORAGE_DRIVER environment variable. The local driver writes to ./data/uploads (override with UPLOAD_DIR); the Docker deployment targets MinIO. The database row only stores the filename, original name, URL, mime type, and size — deleting an account cascades to its asset records.