Convert Thousands of Images Between Formats with a Single API

6 min read Image Transformation

The Format Migration Problem

You have a catalog of 50,000 product images in JPEG. Your frontend team wants WebP. Your mobile team wants AVIF. Your email team still needs JPEG but at lower quality. One source format, three output formats, fifty thousand images.

The classic approach: write a script that shells out to ImageMagick or Sharp, run it on a beefy server for a few hours, hope nothing crashes halfway through, then figure out how to re-run the failures. If you need to convert another batch next month, dust off the script and pray the dependencies still work.

The Image Transformation API converts images between formats with a single HTTP call per image. No library to install. No server to provision. Batch conversion is just a loop.

Converting a Single Image

The building block for any batch operation is a single conversion.

import { IterationLayer } from "iterationlayer";

const client = new IterationLayer({ apiKey: "YOUR_API_KEY" });

const { data: { buffer: webpBase64 } } = await client.transform({
  file: { type: "url", name: "product-001.jpg", url: "https://cdn.example.com/images/product-001.jpg" },
  operations: [
    { type: "convert", format: "webp", quality: 85 },
  ],
});

const webpBuffer = Buffer.from(webpBase64, "base64");

One operation, one API call. The input is a JPEG URL, the output is JSON containing a base64-encoded WebP image. The quality parameter controls the compression level — 85 is a good default for photographs where you want small files without visible artifacts.

Batch Conversion with Parallel Requests

Converting a catalog means converting many images. Promise.all handles the parallelism.

const sourceImages = [
  { name: "product-001.jpg", url: "https://cdn.example.com/images/product-001.jpg" },
  { name: "product-002.jpg", url: "https://cdn.example.com/images/product-002.jpg" },
  { name: "product-003.jpg", url: "https://cdn.example.com/images/product-003.jpg" },
  // ... thousands more
];

const convertImage = async (image: { name: string; url: string }) => {
  const { data: { buffer } } = await client.transform({
    file: { type: "url", name: image.name, url: image.url },
    operations: [
      { type: "convert", format: "webp", quality: 85 },
    ],
  });

  return { name: image.name.replace(/\.jpg$/, ".webp"), buffer: Buffer.from(buffer, "base64") };
};

// Process in chunks to avoid overwhelming the network
const chunkSize = 20;
const results = [];

for (let i = 0; i < sourceImages.length; i += chunkSize) {
  const chunk = sourceImages.slice(i, i + chunkSize);
  const chunkResults = await Promise.all(chunk.map(convertImage));
  results.push(...chunkResults);
}

Processing in chunks of 20 keeps the network manageable. Adjust the chunk size based on your image sizes and network conditions. Smaller images can handle larger chunks. Larger images — 10 MB+ product photos — benefit from smaller chunks.

Adding Resize and Sharpen to the Pipeline

Format conversion rarely happens in isolation. You usually want to resize, sharpen, and convert in the same pass. The API chains up to 30 operations sequentially — each operation’s output feeds into the next.

const operations = [
  { type: "resize", width_in_px: 1200, height_in_px: 900, fit: "inside" },
  { type: "sharpen", sigma: 0.5 },
  { type: "convert", format: "webp", quality: 85 },
];

This constrains the image to a maximum of 1200x900 (without upscaling, thanks to fit: "inside"), sharpens slightly to counteract the softening from downscaling, and converts to WebP. One API call, three operations.

For a batch pipeline that generates optimized web images from high-resolution source files, this is the standard recipe.

Generating Multiple Formats from One Source

Some workflows need the same image in multiple formats. A product page serves WebP to modern browsers and JPEG as a fallback. A content pipeline generates AVIF for the app and JPEG for email.

const sourceUrl = "https://cdn.example.com/images/product-001.jpg";

const formats = [
  { format: "webp", quality: 85, suffix: ".webp" },
  { format: "avif", quality: 70, suffix: ".avif" },
  { format: "jpeg", quality: 85, suffix: ".jpg" },
];

const variants = await Promise.all(
  formats.map(async ({ format, quality, suffix }) => {
    const { data: { buffer } } = await client.transform({
      file: { type: "url", name: "product-001.jpg", url: sourceUrl },
      operations: [
        { type: "resize", width_in_px: 1200, height_in_px: 900, fit: "inside" },
        { type: "convert", format, quality },
      ],
    });

    return { name: `product-001${suffix}`, buffer: Buffer.from(buffer, "base64") };
  })
);

Three parallel requests, three formats, one source image. Each output is independently sized and compressed.

Format Tradeoffs

Choosing the right format depends on your use case.

WebP is the default choice for web images. It produces files 25-34% smaller than JPEG at equivalent visual quality. Browser support is above 97%. It handles both lossy and lossless modes, and supports transparency. Quality 80-85 works well for photographs.

AVIF achieves the best compression — often 50% smaller than JPEG. It supports HDR and wide color gamut. Browser support is above 92% and growing. The tradeoff is encoding time — AVIF is slower to generate than WebP. Use it for hero images and product photos where the file size savings justify the encoding cost. Quality 65-75 is the sweet spot.

JPEG remains the universal fallback. Every browser, every email client, every image viewer supports it. Use quality 80-90 for web display, 70-80 for thumbnails. JPEG doesn’t support transparency — if you need alpha channels, use WebP or PNG.

PNG is lossless, so files are larger. Use it when you need exact pixel reproduction — screenshots, diagrams, graphics with sharp edges, or images with transparency where WebP isn’t an option.

For most web applications, the right strategy is: serve WebP by default, offer AVIF where browser support is confirmed, and keep JPEG as a fallback for legacy contexts like email.

Handling Conversion Failures

In a batch of thousands, some conversions will fail. The source URL might be broken. The image might be corrupted. Building retry logic around the API is straightforward.

const convertWithRetry = async (
  image: { name: string; url: string },
  maxRetries = 3
): Promise<{ name: string; buffer: Buffer } | { name: string; error: string }> => {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const { data: { buffer } } = await client.transform({
        file: { type: "url", name: image.name, url: image.url },
        operations: [{ type: "convert", format: "webp", quality: 85 }],
      });

      return { name: image.name.replace(/\.jpg$/, ".webp"), buffer: Buffer.from(buffer, "base64") };
    } catch {
      // Wait before retrying
      await new Promise((resolve) => setTimeout(resolve, 1_000 * (attempt + 1)));
    }
  }

  return { name: image.name, error: "Failed after max retries" };
};

The retry logic is your code, not buried in a library configuration. You control the backoff, the retry count, and what happens with failures.

The Cost of Self-Hosting

Self-hosted conversion means managing Sharp or ImageMagick on a server. Sharp is a Node.js binding to libvips — fast, but it requires native compilation. ImageMagick is a system package with decades of CVE history. Both need a server with enough CPU and memory to handle your batch sizes.

A 50,000-image conversion job on a self-hosted server means hours of CPU time, careful memory management to avoid OOM kills, and a restart mechanism for crashes. With the API, the same job is 50,000 HTTP requests processed in parallel chunks. Your server sends JSON and receives JSON with base64-encoded images. The compute happens elsewhere.

What’s Next

Converted images work with the same auth and credit pool as Image Generation and Document Extraction — chain them in a single pipeline.

Get Started

Check the docs for the full format conversion reference, including quality guidelines for each format. The TypeScript and Python SDKs handle authentication and response parsing.

Sign up for a free account — no credit card required. Convert a few images, compare the output sizes across formats, and see what works for your pipeline.

Start building in minutes

Free trial included. No credit card required.