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

  1. Upload

    REST, SDK or presigned browser upload

  2. Original stored

    Dreep storage or your own bucket

  3. Request a URL

    ?width=800&format=avif

  4. Generated once

    On the first request

  5. Served from cache

    Every later request

One original, any number of variants, no pre-generation step.

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",
});

URL parameter reference

ParameterValuesWhat it does
width1 to 4000Target width in pixels. Height follows the aspect ratio if omitted.
height1 to 4000Target height in pixels. Width follows the aspect ratio if omitted.
fitcover (default), contain, fill, inside, outsideHow the image fills width x height: crop, letterbox, stretch, or fit within or around the box.
gravitynorth ... northwest, center, attention, entropyWhich part survives a cover crop. attention picks the most salient region, entropy the busiest.
formatjpeg, png, webp, avif, gif, tiffOutput format. There is no automatic format negotiation, so choose explicitly.
quality1 to 100 (default 80)Compression quality for lossy formats.
dpr1 to 3Device pixel ratio. Multiplies width and height, so width=64&dpr=2 returns 128px.
blur0.3 to 100Gaussian blur sigma. Handy for low-quality placeholders.
rotate90, 180, 270Rotates clockwise.
radius1 to 2000, or maxRounds corners. max gives a circle or pill.
bgHex without #, e.g. ffffffFill colour for contain padding or behind rounded corners on formats without alpha.
pA preset keyApplies a saved preset. Any explicit parameter in the same URL overrides the preset value.
cropleft,top,width,heightExact pixel crop. Applied at upload time via the transform option, not on delivery URLs.
Parameters combine in any order, e.g. ?width=1200&height=630&fit=cover&format=avif&quality=85&gravity=attention

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`;

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" });

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" },
});

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

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.