Image CDN API: upload once, transform by URL
Dreep is an image CDN API. Upload an image once through the REST API or an SDK, then request any size, crop or format by adding query parameters such as width, height, format and quality to its CDN URL. Each variant is generated on the first request and cached. WebP and AVIF output, presets, folders and signed URLs for private images are built in.
How the image CDN API works
Upload
REST, SDK or presigned browser upload
Original stored
Dreep storage or your own bucket
Request a URL
?width=800&format=avif
Generated once
On the first request
Served from cache
Every later request
Upload API
Server-side uploads are a single multipart POST. Browser and large uploads use a presigned URL so your API key never reaches the client.
POST /api/v1/upload accepts a file plus an optional folder path, S3-style key or folder ID. Missing folders are created on the fly. Accepted image inputs are JPEG, PNG, WebP, GIF, TIFF, BMP, SVG, HEIC and HEIF, up to 500 MB per file.
The response is the stored asset: its id, a ready-to-use CDN url, original filename, MIME type, format, width, height, size in bytes and folder. Store the id or the url and build every variant from it later. If you pass a transform at upload time, it is applied before storage and the transformed result replaces the original, which is useful for capping huge camera uploads at a sensible maximum width.
import fs from "node:fs";
import { Dreep } from "dreep";
const dreep = new Dreep({ apiKey: process.env.DREEP_API_KEY });
const asset = await dreep.upload({
file: fs.createReadStream("product.jpg"),
folder: "catalogue/shoes",
});
// Pure string work: no network call, safe in a render path
const card = dreep.url(asset, {
width: 600,
height: 600,
fit: "cover",
gravity: "attention",
format: "webp",
});client, err := dreep.New(os.Getenv("DREEP_API_KEY"))
if err != nil {
log.Fatal(err)
}
f, _ := os.Open("product.jpg")
defer f.Close()
asset, err := client.Upload(ctx, dreep.UploadOptions{
File: f,
Filename: "product.jpg",
Destination: dreep.Destination{Folder: "catalogue/shoes"},
})
if err != nil {
log.Fatal(err)
}
card := client.URL(asset.ID, &dreep.Transform{
Width: 600,
Height: 600,
Fit: dreep.FitCover,
Gravity: dreep.GravityAttention,
Format: dreep.FormatWebP,
})curl -X POST https://api.dreep.cloud/api/v1/upload \
-H "Authorization: Bearer $DREEP_API_KEY" \
-F "file=@product.jpg" \
-F "folder=catalogue/shoes"
# Then request any variant of the returned asset
curl -o card.webp \
"https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=600&height=600&fit=cover&gravity=attention&format=webp"URL parameter reference
| Parameter | Values | What it does |
|---|---|---|
| width | 1 to 4000 | Target width in pixels. Height follows the aspect ratio if omitted. |
| height | 1 to 4000 | Target height in pixels. Width follows the aspect ratio if omitted. |
| fit | cover (default), contain, fill, inside, outside | How the image fills width x height: crop, letterbox, stretch, or fit within or around the box. |
| gravity | north ... northwest, center, attention, entropy | Which part survives a cover crop. attention picks the most salient region, entropy the busiest. |
| format | jpeg, png, webp, avif, gif, tiff | Output format. There is no automatic format negotiation, so choose explicitly. |
| quality | 1 to 100 (default 80) | Compression quality for lossy formats. |
| dpr | 1 to 3 | Device pixel ratio. Multiplies width and height, so width=64&dpr=2 returns 128px. |
| blur | 0.3 to 100 | Gaussian blur sigma. Handy for low-quality placeholders. |
| rotate | 90, 180, 270 | Rotates clockwise. |
| radius | 1 to 2000, or max | Rounds corners. max gives a circle or pill. |
| bg | Hex without #, e.g. ffffff | Fill colour for contain padding or behind rounded corners on formats without alpha. |
| p | A preset key | Applies a saved preset. Any explicit parameter in the same URL overrides the preset value. |
| crop | left,top,width,height | Exact pixel crop. Applied at upload time via the transform option, not on delivery URLs. |
Responsive images with srcset and dpr
Because every size is just a URL, a srcset is a loop over widths. Use width descriptors for fluid layouts and dpr for fixed-size elements such as avatars and icons.
// Server component: the SDK runs server-side only
const WIDTHS = [480, 960, 1440];
const HeroImage = ({ asset, alt }) => (
<picture>
<source
type="image/avif"
srcSet={WIDTHS.map((width) =>
`${dreep.url(asset, { width, format: "avif" })} ${width}w`).join(", ")}
sizes="(max-width: 768px) 100vw, 960px"
/>
<img
src={dreep.url(asset, { width: 960, format: "webp" })}
srcSet={WIDTHS.map((width) =>
`${dreep.url(asset, { width, format: "webp" })} ${width}w`).join(", ")}
sizes="(max-width: 768px) 100vw, 960px"
alt={alt}
/>
</picture>
);
// Fixed-size avatar: same CSS size, sharper on dense screens
const avatar = (dpr) =>
dreep.url(asset, { width: 64, height: 64, fit: "cover", dpr, format: "webp" });
const avatarSrcSet = `${avatar(1)} 1x, ${avatar(2)} 2x, ${avatar(3)} 3x`;widths := []int{480, 960, 1440}
parts := make([]string, 0, len(widths))
for _, w := range widths {
u := client.URL(asset.ID, &dreep.Transform{Width: w, Format: dreep.FormatWebP})
parts = append(parts, fmt.Sprintf("%s %dw", u, w))
}
srcset := strings.Join(parts, ", ")
avatar2x := client.URL(asset.ID, &dreep.Transform{
Width: 64, Height: 64, Fit: dreep.FitCover, DPR: 2, Format: dreep.FormatWebP,
})# 64x64 CSS pixels at dpr=2 returns a 128x128 image
curl -o avatar@2x.webp \
"https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=64&height=64&fit=cover&dpr=2&format=webp"AVIF and WebP output
Set format=avif or format=webp on any image URL. Dreep does not pick a format for you from the Accept header, so the reliable pattern is a <picture> element with an AVIF source and a WebP img fallback, as in the example above. Every browser in current use supports at least WebP.
AVIF files are usually smaller than WebP at similar visual quality but take longer to encode, which only matters on the first request for each variant. Start at the default quality of 80 and lower it per use case: thumbnails and blurred placeholders tolerate far lower settings than product photography.
Presets
A preset is a named transform. Define card_sm once, reference it everywhere, and change the recipe later without touching call sites. Free includes 3 presets, Starter 10 and Pro 50.
await dreep.createPreset({
key: "card_sm",
name: "Card, small",
transform: { width: 400, height: 300, fit: "cover", format: "webp" },
});
dreep.url(asset, { preset: "card_sm" });_, err := client.CreatePreset(ctx, dreep.CreatePresetOptions{
Name: "Card, small",
Key: "card_sm",
Transform: &dreep.Transform{
Width: 400, Height: 300, Fit: dreep.FitCover, Format: dreep.FormatWebP,
},
})
u := client.URL(asset.ID, &dreep.Transform{Preset: "card_sm"})# Apply the preset, then override one field for this request
curl -o card.png \
"https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?p=card_sm&format=png"Caching behaviour
The first request for a given combination of asset and parameters generates the variant. Later requests for the same URL are served from cache, so a new size costs one generation, not one per visitor. Public assets are served with a one-year immutable Cache-Control header, so browsers and intermediate caches can keep them too. Assets in Private or Signed folders are served with private, no-store.
Requests that produce or serve a transformed variant count against your monthly image transformation quota: 100 on Free, 10,000 on Starter and 50,000 on Pro. Requests for the untouched original do not. When you hit the limit the API returns LIMIT_EXCEEDED rather than billing overage; you add a one-time pack (+10k for $1.99, +50k for $2.99, +100k for $4.99) or upgrade.
Folders and asset metadata
Folders from paths
Upload to catalogue/shoes/2026 and any missing folders are created. New subfolders inherit their parent's access mode, so a path under a Signed folder never becomes public by accident.
Metadata on every asset
Uploads return width, height, format, MIME type, size in bytes, status and folder ID, so you can store layout dimensions and avoid content shift without a second request.
List and filter
List media by folder, recursively if needed, with page-based pagination and a total count. The JavaScript SDK's listAllMedia walks every page for you.
Delete cleanly
Deleting an asset removes the original and every cached transform of it, and credits the space back against your plan.
Private images and signed URLs
Put images in a Signed folder and they are only fetchable with a time-limited link. The SDKs compute the HMAC-SHA256 signature locally from your signing secret, so there is no network call per link. The signature covers the asset ID and expiry, not the transform, so one signed link can still be resized by the client. Private folders are stricter still: dashboard members only, no public URL at all.
const dreep = new Dreep({
apiKey: process.env.DREEP_API_KEY,
signingSecret: process.env.DREEP_SIGNING_SECRET,
});
// Valid for 15 minutes, resized to 800px wide
const link = dreep.signedUrl(asset, {
expiresIn: 900,
transform: { width: 800, format: "webp" },
});client, err := dreep.New(
os.Getenv("DREEP_API_KEY"),
dreep.WithSigningSecret(os.Getenv("DREEP_SIGNING_SECRET")),
)
// dreep-go signs the original only, so pass nil. Sign transformed
// links with the JavaScript SDK until the Go SDK signs parameters.
link, err := client.SignedURL(asset.ID, 15*time.Minute, nil)# exp and sig are computed locally by your backend (HMAC-SHA256)
curl -o private.webp \
"https://cdn.dreep.cloud/api/v1/fetch/2f928a3f.jpg?width=800&format=webp&exp=1774118400&sig=a3f9e2b1..."Who this is for
- Web and mobile teams that want one stored original and every size by URL.
- Products with user-generated images that need AVIF or WebP without a build step.
- Developers working in Node, TypeScript or Go who want typed SDKs, or anything that can make an HTTP request.
- Teams that want images in their own S3-compatible bucket on Starter or Pro.
When Dreep is not the right fit
- You need automatic format negotiation from the Accept header. Dreep needs an explicit format.
- You need watermarks, text overlays or compositing. Dreep does not offer them.
- You need an official Python, Ruby or PHP SDK. Only JavaScript and Go SDKs exist; other languages use the REST API.
Frequently asked questions
What is the difference between an image CDN and an image transformation API?
An image CDN delivers images from cache over HTTP. An image transformation API changes them: resizing, cropping, format conversion. Dreep does both through one URL, the transformation runs on the first request for a variant and the result is cached for every later request.
Does Dreep choose WebP or AVIF automatically?
No. There is no format=auto. Request format=avif or format=webp explicitly and use a picture element with an AVIF source and a WebP fallback.
What is the largest image I can request?
width and height are each capped at 4000 pixels, and dpr at 3. Uploads can be up to 500 MB per file.
Do cached requests still count towards my transformation quota?
Requests for a transformed variant count against your monthly image transformation quota. Requests for the untouched original do not. Public variants are also served with a long-lived Cache-Control header, so repeat views by the same browser are answered from its cache.
Can I resize images in a signed folder?
Yes. The signature covers the asset ID and expiry only, so you can append width, format or any other parameter to a signed URL without generating a new signature.
Which SDKs are available?
An official JavaScript and TypeScript SDK (npm install dreep) and a Go SDK (github.com/IndigoSoftwares21/dreep-go). Both have zero runtime dependencies. Any other language can call the REST API directly.
Related
Image transformations for Cloudflare R2
Keep originals in your own R2 bucket and resize them by URL.
Secure file uploads
Presigned browser uploads without exposing your API key.
Video transcoding API
MP4, WebM, HLS, GIF and thumbnails from one upload.
SDKs
Typed JavaScript and Go clients with zero dependencies.
Pricing
Plans from $0 with 100 to 50,000 image transformations a month.
Image transformation docs
Every parameter with worked examples.
Resize your first image by URL
The Free plan includes 1 GB of storage and 100 image transformations a month. Upload one image and try width, format and dpr on it.