1. Overview
MagicStack Imaginary is a lightweight image-hosting and on-the-fly transformation service. You upload an image once and receive a short image code that you embed wherever you need the image. Resized or quality-adjusted variants are generated on first request and cached automatically.
All endpoint paths below are shown relative to the service root.
X-Tenant-ID header that identifies your application. Image retrieval is
unauthenticated so that images can be embedded in any web page or email.
2. Uploading Images
The upload endpoint supports two methods: multipart form upload (standard file upload) and JSON base64 upload (useful for API integrations).
Method 1: Multipart Form Upload
Send a multipart/form-data POST request with your image file to receive a
unique image code.
Request Headers
| Header | Required | Description |
|---|---|---|
X-Tenant-ID |
Yes | Your application's tenant identifier. |
Content-Type |
Yes | Must be multipart/form-data (set automatically by most HTTP clients). |
Request Body (form fields)
| Field | Required | Description |
|---|---|---|
file |
Yes | The image file to upload (JPEG, PNG, GIF, WebP, etc.). |
folder |
No | Logical grouping for the image (e.g. avatars, products). Defaults to default. |
Example – cURL
curl -X POST https://your-domain.com/upload \
-H "X-Tenant-ID: my-app" \
-F "file=@/path/to/photo.jpg" \
-F "folder=products"
Example – JavaScript (fetch)
const form = new FormData();
form.append('file', fileInput.files[0]);
form.append('folder', 'products');
const response = await fetch('/upload', {
method: 'POST',
headers: { 'X-Tenant-ID': 'my-app' },
body: form,
});
const { image_code } = await response.json();
console.log('Uploaded image code:', image_code);
Method 2: JSON Base64 Upload
Send a JSON payload containing a base64-encoded image string. This method is useful for API integrations, mobile apps, or when the image is already in memory as base64.
Request Headers
| Header | Required | Description |
|---|---|---|
X-Tenant-ID |
Yes | Your application's tenant identifier. |
Content-Type |
Yes | Must be application/json. |
Request Body (JSON fields)
| Field | Required | Description |
|---|---|---|
file_base64 |
Yes* | Base64-encoded image string (raw or data URL format). |
image_base64 |
Yes* | Alternative field name for base64 image string. |
base64 |
Yes* | Alternative field name for base64 image string. |
file |
Yes* | Alternative field name for base64 image string. |
filename |
No | Original filename (used to infer file extension). If omitted, extension is inferred from data URL or defaults to jpg. |
folder |
No | Logical grouping for the image. Defaults to default. |
* One of the base64 fields is required. The endpoint checks file_base64,
image_base64, base64, and file in that order.
Base64 Format
The base64 string can be provided in two formats:
- Data URL:
data:image/png;base64,iVBORw0KG... - Raw base64:
iVBORw0KG...
Example – cURL (data URL)
curl -X POST https://your-domain.com/upload \
-H "X-Tenant-ID: my-app" \
-H "Content-Type: application/json" \
-d '{
"file_base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVR4AWP4DwQACfsD/c8LaHIAAAAASUVORK5CYII=",
"filename": "tiny.png",
"folder": "avatars"
}'
Example – JavaScript (fetch)
// Convert file to base64
function fileToBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
const base64String = await fileToBase64(fileInput.files[0]);
const response = await fetch('/upload', {
method: 'POST',
headers: {
'X-Tenant-ID': 'my-app',
'Content-Type': 'application/json',
},
body: JSON.stringify({
file_base64: base64String,
filename: fileInput.files[0].name,
folder: 'products',
}),
});
const { image_code } = await response.json();
console.log('Uploaded image code:', image_code);
Response
Both methods return HTTP 201 Created with a JSON body containing the new image code:
{
"image_code": "abc123XYZ789"
}
JSON_BODY_LIMIT environment variable
is configured appropriately (default: 15mb).
3. Linking to Images
Once you have an image code you can reference the original image directly in an
<img> tag or any URL.
Path Parameters
| Parameter | Description |
|---|---|
imageCode |
The 12-character code returned by the upload endpoint. |
Response
The original image file is streamed back with the correct Content-Type header
(e.g. image/jpeg, image/png).
If the image code is not found, a placeholder JPEG image is returned with HTTP
404.
HTML Example
<img src="/abc123XYZ789" alt="Product photo" />
Markdown Example

4. Resizing & Transforming Images in Links
Append a transform segment before the image code to receive a resized and/or quality-adjusted JPEG variant. Variants are generated on first request and cached for subsequent calls.
Transform Format
The transform is a comma-separated list of key_value pairs. All parameters are
optional but at least one must be present.
| Parameter | Example | Description |
|---|---|---|
w_<pixels> |
w_400 |
Maximum output width in pixels. |
h_<pixels> |
h_300 |
Maximum output height in pixels. |
q_<1-100> |
q_75 |
JPEG quality (1 = lowest, 100 = highest). Defaults to 50. |
Examples
| URL | Effect |
|---|---|
/w_400/abc123XYZ789 |
Resize to max width 400 px, quality 50 (default) |
/h_300/abc123XYZ789 |
Resize to max height 300 px, quality 50 (default) |
/w_800,h_600/abc123XYZ789 |
Resize to fit within 800 × 600 px |
/w_400,q_75/abc123XYZ789 |
Resize to max width 400 px at 75% JPEG quality |
/q_90/abc123XYZ789 |
Original dimensions, higher quality (90%) |
HTML Example
<!-- Thumbnail: 200 px wide, default quality -->
<img src="/w_200/abc123XYZ789" alt="Thumbnail" />
<!-- Full-width hero: max 1200 px wide, quality 80 -->
<img src="/w_1200,q_80/abc123XYZ789" alt="Hero" />
Responsive Images Example
<img
src="/w_800/abc123XYZ789"
srcset="
/w_400/abc123XYZ789 400w,
/w_800/abc123XYZ789 800w,
/w_1200/abc123XYZ789 1200w
"
sizes="(max-width: 600px) 400px, (max-width: 1000px) 800px, 1200px"
alt="Responsive product image"
/>
5. Automatic Image Cleanup
To keep storage usage under control, the service automatically removes cached transform variants that have not been accessed recently.
What gets cleaned up
Only variant files (the resized/transformed copies) are eligible for cleanup. The original uploaded image is never deleted automatically.
Cleanup schedule
| Setting | Default | Description |
|---|---|---|
| Stale threshold | 7 days | A cached variant is removed if its last_accessed timestamp is older than 7 days. |
| Cleanup interval | Every hour |
The cleanup job runs periodically. Override with the
CLEANUP_INTERVAL_MS environment variable (value in milliseconds).
|
What happens after cleanup
If a client requests a variant that was previously cleaned up, the service regenerates it on-the-fly from the original image and caches it again – so no data is permanently lost.
6. Error Responses
All errors use a consistent JSON envelope:
{
"error": {
"message": "Human-readable description of the error"
}
}
Common HTTP status codes
| Code | Meaning |
|---|---|
400 | Bad request – missing or invalid parameters (e.g. no file, missing X-Tenant-ID, invalid transform string). |
404 | Image not found – a placeholder JPEG is returned instead of an error body. |
429 | Too many requests – rate limit exceeded. Wait before retrying. |
500 | Internal server error – unexpected failure on the server side. |