API Reference
All upload and storage endpoints — admin and mobile — with request/response shapes.
Upload API Reference
Audience: backend, frontend Scope:
apps/api/src/modules/upload/upload.controller.ts,mobile-upload.controller.ts
All endpoints require Authorization: Bearer <token>.
Admin Endpoints — /upload
POST /upload
Upload a single file. No file-type restriction. Size limit is controlled by UPLOAD_MAX_FILE_SIZE_MB (default 50 MB).
Query params
| Param | Type | Default | Description |
|---|---|---|---|
uploadType | UploadType enum | image | Destination folder and access visibility |
optimize | boolean | true | Run image optimisation (Sharp → WebP) |
Body — multipart/form-data
| Field | Required |
|---|---|
file | ✅ |
Response 200
{
"message": "File uploaded successfully",
"data": {
"filename": "sdcupH633744750-photo.webp",
"originalName": "photo.jpg",
"mimeType": "image/webp",
"size": 48320,
"relativePath": "uploads/image/sdcupH633744750-photo.webp",
"url": "http://minio:9000/apple/uploads/image/sdcupH633744750-photo.webp?X-Amz-...",
"expiresAt": 1712250000000
}
}For public upload types (avatar, landing, thumbnail):
{
"data": {
"relativePath": "public/avatar/sdcupH633744750-photo.webp",
"url": "http://minio:9000/apple/public/avatar/sdcupH633744750-photo.webp",
"expiresAt": null
}
}Store
relativePathin the DB, neverurl. Signed URLs expire after ~2 hours. Public URLs are permanent but the bucket endpoint may change.
POST /upload/multiple
Upload up to 10 files in one request.
Query params — same as single upload.
Body — multipart/form-data
| Field | Required | Note |
|---|---|---|
files | ✅ | 1–10 files |
Response 200 — array of UploadResultDto (same shape as single upload).
GET /upload/signed-url/*key
Resolve a stored relativePath to a viewable URL. Use this when a previously issued signed URL has expired and the frontend needs a fresh one.
Path param — key: the relativePath value from the DB (supports slashes, e.g. uploads/image/abc.webp).
Query params
| Param | Type | Description |
|---|---|---|
expiresIn | number (seconds) | Override the default signed URL TTL |
Response 200
{
"message": "Signed URL generated",
"data": {
"url": "http://minio:9000/apple/uploads/image/abc.webp?X-Amz-...",
"expiresAt": 1712250000000
}
}For public keys (public/*):
{
"message": "Local URL resolved",
"data": {
"url": "http://minio:9000/apple/public/avatar/abc.webp",
"expiresAt": null
}
}POST /upload/signed-urls
Batch URL resolution — more efficient than calling GET /upload/signed-url once per file.
Body application/json
{
"keys": [
"uploads/image/abc.webp",
"public/avatar/xyz.webp",
"uploads/file/report.pdf"
],
"expiresIn": 3600
}Response 200
{
"message": "URLs resolved",
"data": {
"uploads/image/abc.webp": {
"url": "http://minio:9000/apple/uploads/image/abc.webp?X-Amz-...",
"expiresAt": 1712250000000
},
"public/avatar/xyz.webp": {
"url": "http://minio:9000/apple/public/avatar/xyz.webp",
"expiresAt": null
},
"uploads/file/report.pdf": {
"url": "http://minio:9000/apple/uploads/file/report.pdf?X-Amz-...",
"expiresAt": 1712250000000
}
}
}GET /upload/file/*key
Backend proxy — streams a file through the NestJS server. Use this when you do not want to expose MinIO URLs to the client at all.
Trade-off: every request goes through NestJS, adding latency and server load. Suitable for low-traffic or security-sensitive files.
Response — raw file stream with Cache-Control: private, max-age=3600.
Mobile Endpoints — /mobile/uploads
All mobile endpoints are rate-limited per IP:
| Endpoint | Limit |
|---|---|
POST /mobile/uploads | 10 requests / 60 s |
GET /mobile/uploads/signed-url/*key | 60 requests / 60 s |
POST /mobile/uploads
Single file upload for authenticated customers. Fixed 5 MB size limit (not configurable per-env).
Query params
| Param | Type | Default | Description |
|---|---|---|---|
uploadType | CustomerUploadType | image | avatar, image, or file only |
optimize | boolean | true | Image optimisation |
landing,thumbnail, andvideoare not available on this endpoint. Attempts to pass them will fail class-validator with a 400.
Body — multipart/form-data, field name file.
Response — same UploadResultDto shape as admin upload.
GET /mobile/uploads/signed-url/*key
Identical logic to GET /upload/signed-url/*key but rate-limited for customer usage.
Upload Flow — Sequence
Error Responses
| Status | Condition |
|---|---|
400 | No file provided, or uploadType fails enum validation |
413 | File exceeds size limit (Multer or MinIO) |
502 | Remote storage rejected the upload (auth error, quota, etc.) |
429 | Rate limit exceeded (mobile endpoints only) |