| Name | Type | Dimensions | Size | Uploaded | Actions |
|---|
Enter your pass code to unlock the storage explorer
| Name | Type | Dimensions | Size | Uploaded | Actions |
|---|
...
| Original Name | ... |
| Storage Path | ... |
| MIME / Type | ... |
| Dimensions | ... |
| File Size | ... |
| Visibility | ... |
| SHA-256 Hash | ... |
| Uploaded At | ... |
KhmerPlaces Files is a database-less, high-performance file server and image CDN. Files are stored directly on disk with companion JSON metadata sidecars (.meta.json), supporting multi-bucket isolation, on-the-fly image resizing variants, and partial-content audio/video streaming.
When FILE_SERVER_API_KEY is configured in your server's .env, all write operations (upload, delete, generate signed URLs) require authentication. Public file reads do not require an API key.
| Method | Header / Parameter | Example |
|---|---|---|
| Header (Recommended) | X-API-Key |
X-API-Key: your-secret-api-key |
| Bearer Token | Authorization |
Authorization: Bearer your-secret-api-key |
| Query String | api_key |
?api_key=your-secret-api-key |
https://files.khmerplaces.com/api/v1
{
"success": true,
"message": "File uploaded successfully.",
"data": { ... },
"errors": null
}
/api/v1/files/upload
Upload single or multiple files (up to 500MB each) using standard multipart/form-data.
| Field | Type | Default | Description |
|---|---|---|---|
file or files[] |
File / Binary | Required | Single file or array of files to upload. Max size: 500MB per file. |
bucket |
String | public |
Target bucket (e.g. public, media, avatars, documents, or dynamic buckets like testing). |
visibility |
String | public |
Set to public (direct CDN access) or private (requires signed URL). |
path or folder |
String | YYYY/MM |
Optional custom subfolder path (e.g. products/12). Defaults to year/month on disk. |
curl --location 'https://files.khmerplaces.com/api/v1/files/upload' \ --header 'X-API-Key: your-api-key' \ --form 'bucket="testing"' \ --form 'visibility="public"' \ --form 'file=@"/Users/puchan/Desktop/song.wav"'
const formData = new FormData();
formData.append('bucket', 'testing');
formData.append('visibility', 'public');
formData.append('file', fileInput.files[0]);
const res = await fetch('https://files.khmerplaces.com/api/v1/files/upload', {
method: 'POST',
headers: {
'X-API-Key': 'your-api-key'
},
body: formData
});
const json = await res.json();
console.log('Uploaded CDN URL:', json.data.url);
$ch = curl_init('https://files.khmerplaces.com/api/v1/files/upload');
$cfile = new CURLFile('/path/to/song.wav', 'audio/wav', 'song.wav');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['X-API-Key: your-api-key'],
CURLOPT_POSTFIELDS => [
'bucket' => 'testing',
'visibility' => 'public',
'file' => $cfile,
],
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
/api/v1/files/{bucket}/{path}?w=...&h=...
Transform any uploaded image on the fly with zero backend code. Resized variants are automatically cached on disk and served with 1-year browser cache headers (Cache-Control: public, max-age=31536000).
| Parameter | Type | Options / Example | Description |
|---|---|---|---|
w |
Integer | w=400 |
Target width in pixels (1 - 4096). |
h |
Integer | h=300 |
Target height in pixels (1 - 4096). |
dim |
String | dim=800x600 |
Shorthand for width and height in a single parameter. |
fit |
String | cover, contain, fill, scale, pad |
Resizing fit mode. Default is cover (crops cleanly to fill dimensions). |
fm |
String | webp, avif, jpeg, png, gif |
Target output format conversion. webp and avif drastically reduce file sizes. |
q |
Integer | q=80 (1-100) |
Output compression quality. Default is 85. |
grayscale |
Boolean | grayscale=1 |
Convert image into stylish black & white. |
blur |
Integer | blur=15 (1-100) |
Apply gaussian blur (great for placeholder backgrounds). |
<!-- Responsive WebP Thumbnail -->
<img src="https://files.khmerplaces.com/api/v1/files/public/2026/09/sample.jpg?w=400&h=300&fit=cover&fm=webp&q=80"
alt="Preview"
loading="lazy" />
<!-- Blurred Placeholder -->
<img src="https://files.khmerplaces.com/api/v1/files/public/2026/09/sample.jpg?w=100&h=60&blur=20&fm=webp" />
/api/v1/files/{bucket}/{path}
Audio (.wav, .mp3, .m4a, .ogg) and video (.mp4, .webm, .mov) files support HTTP Byte-Range streaming (206 Partial Content). Users can instantly seek/scrub forward and backward without waiting for the full file to buffer.
Range: bytes=0-1048575).?download=1 to any URL to trigger automatic download with original filename preserved in Content-Disposition.<!-- Native In-Browser Audio Player with Seeking --> <audio controls src="https://files.khmerplaces.com/api/v1/files/testing/2026/09/sound.wav"></audio> <!-- Native Video Player --> <video controls src="https://files.khmerplaces.com/api/v1/files/media/2026/09/clip.mp4" width="640"></video> <!-- Direct Download Link --> <a href="https://files.khmerplaces.com/api/v1/files/testing/2026/09/sound.wav?download=1" download> Download Audio File </a>
/api/v1/files/{bucket}/signed-url
Private files cannot be accessed via direct CDN links (returns 403 Forbidden). Generate temporary HMAC-SHA256 signed URLs that grant secure, time-limited access.
| Field | Type | Default | Description |
|---|---|---|---|
path |
String | Required | Relative storage path of the file (e.g. 2026/09/uuid.pdf). |
expires_in_minutes |
Integer | 60 |
Minutes until the signed URL expires (min 1, max 10,080 [7 days]). |
curl --location 'https://files.khmerplaces.com/api/v1/files/testing/signed-url' \
--header 'X-API-Key: your-api-key' \
--header 'Content-Type: application/json' \
--data '{
"path": "2026/09/secret-report.pdf",
"expires_in_minutes": 120
}'
{
"success": true,
"signed_url": "https://files.khmerplaces.com/api/v1/files/testing/2026/09/secret-report.pdf?expires=1789078800&signature=3a8b2c...",
"expires_at": "2026-09-11T07:45:00+07:00"
}
List of all RESTful routes available on KhmerPlaces Files API:
| Method | Route | Auth | Description |
|---|---|---|---|
| GET | /api/v1/buckets |
Public | List configured & dynamic buckets with metrics and folder trees. |
| GET | /api/v1/stats |
Public | Global file count, disk usage, and cached variants count. |
| POST | /api/v1/files/upload |
API Key | Upload one or more files (up to 500MB) with visibility option. |
| GET | /api/v1/files/{bucket} |
Public | Paginated file list with lazy metadata, search, type, and date filters. |
| GET | /api/v1/files/{bucket}/{path} |
Public / Signature | Serve/stream file, apply dynamic image transformations, or partial content. |
| GET | /api/v1/files/{bucket}/{path}/info |
Public | Read full sidecar metadata (dimensions, EXIF, SHA-256, original name). |
| POST | /api/v1/files/{bucket}/signed-url |
API Key | Generate temporary HMAC-SHA256 signed URL for private files. |
| DELETE | /api/v1/files/{bucket}/{path} |
API Key | Delete original file, sidecar metadata, and purge cached image variants. |