Upload Image
curl --request POST \
--url https://api.sublay.io/api/v6/:projectId/api/v7/storage/images \
--header 'Content-Type: application/json' \
--data '
{
"mode": "<string>",
"dimensions": {},
"aspectRatio": {},
"widths": {},
"heights": {},
"sizes": {},
"aspectRatios": [
{}
],
"fit": "<string>",
"quality": 123,
"format": "<string>",
"stripExif": true,
"pathParts": [
{}
],
"entityId": "<string>",
"commentId": "<string>",
"spaceId": "<string>",
"userId": "<string>"
}
'import requests
url = "https://api.sublay.io/api/v6/:projectId/api/v7/storage/images"
payload = {
"mode": "<string>",
"dimensions": {},
"aspectRatio": {},
"widths": {},
"heights": {},
"sizes": {},
"aspectRatios": [{}],
"fit": "<string>",
"quality": 123,
"format": "<string>",
"stripExif": True,
"pathParts": [{}],
"entityId": "<string>",
"commentId": "<string>",
"spaceId": "<string>",
"userId": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
mode: '<string>',
dimensions: {},
aspectRatio: {},
widths: {},
heights: {},
sizes: {},
aspectRatios: [{}],
fit: '<string>',
quality: 123,
format: '<string>',
stripExif: true,
pathParts: [{}],
entityId: '<string>',
commentId: '<string>',
spaceId: '<string>',
userId: '<string>'
})
};
fetch('https://api.sublay.io/api/v6/:projectId/api/v7/storage/images', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sublay.io/api/v6/:projectId/api/v7/storage/images",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'mode' => '<string>',
'dimensions' => [
],
'aspectRatio' => [
],
'widths' => [
],
'heights' => [
],
'sizes' => [
],
'aspectRatios' => [
[
]
],
'fit' => '<string>',
'quality' => 123,
'format' => '<string>',
'stripExif' => true,
'pathParts' => [
[
]
],
'entityId' => '<string>',
'commentId' => '<string>',
'spaceId' => '<string>',
'userId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.sublay.io/api/v6/:projectId/api/v7/storage/images"
payload := strings.NewReader("{\n \"mode\": \"<string>\",\n \"dimensions\": {},\n \"aspectRatio\": {},\n \"widths\": {},\n \"heights\": {},\n \"sizes\": {},\n \"aspectRatios\": [\n {}\n ],\n \"fit\": \"<string>\",\n \"quality\": 123,\n \"format\": \"<string>\",\n \"stripExif\": true,\n \"pathParts\": [\n {}\n ],\n \"entityId\": \"<string>\",\n \"commentId\": \"<string>\",\n \"spaceId\": \"<string>\",\n \"userId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.sublay.io/api/v6/:projectId/api/v7/storage/images")
.header("Content-Type", "application/json")
.body("{\n \"mode\": \"<string>\",\n \"dimensions\": {},\n \"aspectRatio\": {},\n \"widths\": {},\n \"heights\": {},\n \"sizes\": {},\n \"aspectRatios\": [\n {}\n ],\n \"fit\": \"<string>\",\n \"quality\": 123,\n \"format\": \"<string>\",\n \"stripExif\": true,\n \"pathParts\": [\n {}\n ],\n \"entityId\": \"<string>\",\n \"commentId\": \"<string>\",\n \"spaceId\": \"<string>\",\n \"userId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sublay.io/api/v6/:projectId/api/v7/storage/images")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"mode\": \"<string>\",\n \"dimensions\": {},\n \"aspectRatio\": {},\n \"widths\": {},\n \"heights\": {},\n \"sizes\": {},\n \"aspectRatios\": [\n {}\n ],\n \"fit\": \"<string>\",\n \"quality\": 123,\n \"format\": \"<string>\",\n \"stripExif\": true,\n \"pathParts\": [\n {}\n ],\n \"entityId\": \"<string>\",\n \"commentId\": \"<string>\",\n \"spaceId\": \"<string>\",\n \"userId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyStorage Endpoints
Upload Image
Upload an image with server-side processing and named variant generation
POST
/
:projectId
/
api
/
v7
/
storage
/
images
Upload Image
curl --request POST \
--url https://api.sublay.io/api/v6/:projectId/api/v7/storage/images \
--header 'Content-Type: application/json' \
--data '
{
"mode": "<string>",
"dimensions": {},
"aspectRatio": {},
"widths": {},
"heights": {},
"sizes": {},
"aspectRatios": [
{}
],
"fit": "<string>",
"quality": 123,
"format": "<string>",
"stripExif": true,
"pathParts": [
{}
],
"entityId": "<string>",
"commentId": "<string>",
"spaceId": "<string>",
"userId": "<string>"
}
'import requests
url = "https://api.sublay.io/api/v6/:projectId/api/v7/storage/images"
payload = {
"mode": "<string>",
"dimensions": {},
"aspectRatio": {},
"widths": {},
"heights": {},
"sizes": {},
"aspectRatios": [{}],
"fit": "<string>",
"quality": 123,
"format": "<string>",
"stripExif": True,
"pathParts": [{}],
"entityId": "<string>",
"commentId": "<string>",
"spaceId": "<string>",
"userId": "<string>"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
mode: '<string>',
dimensions: {},
aspectRatio: {},
widths: {},
heights: {},
sizes: {},
aspectRatios: [{}],
fit: '<string>',
quality: 123,
format: '<string>',
stripExif: true,
pathParts: [{}],
entityId: '<string>',
commentId: '<string>',
spaceId: '<string>',
userId: '<string>'
})
};
fetch('https://api.sublay.io/api/v6/:projectId/api/v7/storage/images', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.sublay.io/api/v6/:projectId/api/v7/storage/images",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'mode' => '<string>',
'dimensions' => [
],
'aspectRatio' => [
],
'widths' => [
],
'heights' => [
],
'sizes' => [
],
'aspectRatios' => [
[
]
],
'fit' => '<string>',
'quality' => 123,
'format' => '<string>',
'stripExif' => true,
'pathParts' => [
[
]
],
'entityId' => '<string>',
'commentId' => '<string>',
'spaceId' => '<string>',
'userId' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.sublay.io/api/v6/:projectId/api/v7/storage/images"
payload := strings.NewReader("{\n \"mode\": \"<string>\",\n \"dimensions\": {},\n \"aspectRatio\": {},\n \"widths\": {},\n \"heights\": {},\n \"sizes\": {},\n \"aspectRatios\": [\n {}\n ],\n \"fit\": \"<string>\",\n \"quality\": 123,\n \"format\": \"<string>\",\n \"stripExif\": true,\n \"pathParts\": [\n {}\n ],\n \"entityId\": \"<string>\",\n \"commentId\": \"<string>\",\n \"spaceId\": \"<string>\",\n \"userId\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.sublay.io/api/v6/:projectId/api/v7/storage/images")
.header("Content-Type", "application/json")
.body("{\n \"mode\": \"<string>\",\n \"dimensions\": {},\n \"aspectRatio\": {},\n \"widths\": {},\n \"heights\": {},\n \"sizes\": {},\n \"aspectRatios\": [\n {}\n ],\n \"fit\": \"<string>\",\n \"quality\": 123,\n \"format\": \"<string>\",\n \"stripExif\": true,\n \"pathParts\": [\n {}\n ],\n \"entityId\": \"<string>\",\n \"commentId\": \"<string>\",\n \"spaceId\": \"<string>\",\n \"userId\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.sublay.io/api/v6/:projectId/api/v7/storage/images")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"mode\": \"<string>\",\n \"dimensions\": {},\n \"aspectRatio\": {},\n \"widths\": {},\n \"heights\": {},\n \"sizes\": {},\n \"aspectRatios\": [\n {}\n ],\n \"fit\": \"<string>\",\n \"quality\": 123,\n \"format\": \"<string>\",\n \"stripExif\": true,\n \"pathParts\": [\n {}\n ],\n \"entityId\": \"<string>\",\n \"commentId\": \"<string>\",\n \"spaceId\": \"<string>\",\n \"userId\": \"<string>\"\n}"
response = http.request(request)
puts response.read_bodyUploads an image, processes it with Sharp, and stores the original plus all requested variants. Returns proxy URLs for all variants immediately.
This endpoint accepts
multipart/form-data. Requires an authenticated user.
Rate limit: 20 requests per 5 minutes.
Body Parameters
file
required
The image file to upload. Must be a valid image format recognized by Sharp (JPEG, PNG, WebP, AVIF, TIFF, GIF, SVG). Maximum 50 MB.
string
required
Processing mode. One of:
exact-dimensions, aspect-ratio-width-based, aspect-ratio-height-based, original-aspect, multi-aspect-ratio.object
Required for
exact-dimensions mode. An object mapping variant names to { width, height } pairs.{ "thumbnail": { "width": 64, "height": 64 }, "card": { "width": 400, "height": 300 } }
object
Required for
aspect-ratio-width-based and aspect-ratio-height-based modes. { width, height } ratio.object
Required for
aspect-ratio-width-based mode. An object mapping variant names to pixel widths.object
Required for
aspect-ratio-height-based mode. An object mapping variant names to pixel heights.object
Required for
original-aspect and multi-aspect-ratio modes. An object mapping variant names to longest-edge pixel sizes.array
Required for
multi-aspect-ratio mode. Array of { width, height } aspect ratio objects.string
Sharp fit strategy. Defaults to
cover. Valid values depend on the mode:exact-dimensions,aspect-ratio-width-based,aspect-ratio-height-based,multi-aspect-ratio:cover,contain,inside, oroutsideoriginal-aspect:insideoroutsideonly (cover/containare not valid for this mode)
number
Output quality (1–100). Applies to JPEG and WebP. Defaults to
85.string
Output format:
webp, jpeg, png, or original. Defaults to webp.boolean
Remove EXIF metadata from output. Defaults to
true.array
Storage path segments as a JSON-encoded array. Defaults to
["images", "<fileId>"].string
Associates the file with an entity for cascade deletion.
string
Associates the file with a comment for cascade deletion.
string
Associates the file with a space.
string
Attribute the upload to a specific user. With a user token the upload is
already attributed to the caller; only service/master keys may name a
different user. Omit for a backend/project-owned file (no owning user).
Response
{
"fileId": "uuid",
"type": "image",
"originalPath": "/internal/files/...",
"originalSize": 204800,
"originalWidth": 1920,
"originalHeight": 1080,
"variants": {
"thumbnail": {
"publicPath": "/internal/files/...",
"width": 64,
"height": 64,
"size": 3200,
"format": "webp"
}
},
"format": "webp",
"quality": 85,
"createdAt": "2025-01-01T00:00:00.000Z"
}
Error Responses
No File — 400 Bad Request
No File — 400 Bad Request
{ "error": "No file provided", "code": "storage/no-file" }
File Too Large — 413
File Too Large — 413
{ "error": "File size exceeds the allowed limit of 50MB", "code": "storage/file-too-large" }
Invalid Image — 422
Invalid Image — 422
{ "error": "Invalid image file", "code": "storage/invalid-image" }

