How to Convert Between JPG, PNG, BMP, GIF, and TIFF Image Formats via API

How to Convert Between JPG, PNG, BMP, GIF, and TIFF Image Formats via API

# python# api# tutorial# automation
How to Convert Between JPG, PNG, BMP, GIF, and TIFF Image Formats via APIPDF4me

Somewhere in your pipeline, a file shows up in the wrong format. A designer hands over a logo as PNG,...

Somewhere in your pipeline, a file shows up in the wrong format. A designer hands over a logo as PNG, your e-commerce platform wants JPG. A scanner spits out TIFF, your web app only renders GIF or WebP. A customer uploads a screenshot as BMP because that's what their tool defaulted to, and now your ingestion job chokes on it.

The usual fix is embarrassing for how common it is: someone opens the file in an image editor, exports it as the right format, and re-uploads it by hand. That works exactly once. It does not scale past a handful of files, and it definitely does not survive being part of an automated pipeline that runs unattended at 2 a.m.

This post walks through PDF4me's Convert Image Format REST endpoint end to end: the exact request shape (verified against the live sample code, not just the docs page), the transparency gotcha that quietly wrecks batch jobs, and a full working Python example that handles both the instant and the asynchronous response paths.

What the Endpoint Actually Does

PDF4me's Convert Image Format endpoint takes one image in one format and returns the same image in a different format:

POST https://api.pdf4me.com/api/v2/ConvertImageFormat
Enter fullscreen mode Exit fullscreen mode

It supports six formats on both the input and output side: JPG, PNG, WEBP, TIFF, BMP, and GIF. That is thirty possible conversion pairs from a single endpoint, not six separate endpoints for each format you might need.

The Request Fields, and a Casing Gotcha Worth Knowing About

Here is where it is worth being precise, because the docs page and the actual working sample code disagree on one detail. The docs page lists the format fields as CurrentImageFormat and NewImageFormat (capitalized). The live, working Python sample in PDF4me's own pdf4me-api-samples repo sends them lowercase, as currentImageFormat and newImageFormat, and also includes a field the docs page's simplified example leaves out entirely: isAsync.

The full request body, as verified against that live sample:

  • docName: the source filename, extension included.
  • docContent: the image file, Base64-encoded, up to 50 MB.
  • currentImageFormat: the actual format of the file you are sending (BMP, GIF, JPG, PNG, or TIFF).
  • newImageFormat: the format you want back.
  • isAsync: true to let the API process the conversion asynchronously and hand you a polling URL instead of blocking the request.

If you are copying field names from the docs page's example JSON rather than a working sample, this is the kind of mismatch that produces a confusing 400 instead of a clean conversion. When the two sources disagree, trust the sample code that has actually been run against the live API.

currentImageFormat still has to match the real binary format of docContent, not just what the filename claims. A file that is secretly a PNG wearing a .jpg extension will fail to decode rather than convert gracefully, so validate the real format before you submit it if your pipeline accepts files from users or third parties.

Two Response Shapes, Not One

This is the second thing the simplified docs example glosses over: the endpoint can respond two different ways, and your code has to handle both.

  • HTTP 200 means the conversion finished synchronously. The response body is the raw converted image bytes, not a JSON envelope. You write response.content straight to a file.
  • HTTP 202 means PDF4me accepted the job and is processing it asynchronously. The response carries a Location header with a polling URL. You poll that URL until it returns 200 (done, same raw-bytes body) or you exhaust your retry budget.

A code sample that only handles the 200 case will work in casual testing and then silently break the day a larger or slower image gets queued asynchronously instead.

The Transparency Problem Nobody Warns You About

JPG does not support an alpha channel. If you are converting a PNG or GIF that has transparent areas into JPG, that transparency does not survive the trip. It gets replaced with a solid background, typically white, wherever the image used to be transparent.

This is correct behavior, not a bug. JPG's compression format simply has no concept of "this pixel is see-through." But it is the kind of thing that looks fine in a quick test and then quietly wrecks a batch job three weeks later, when someone converts five hundred logos to JPG for a print vendor and half of them come back with a white box where the transparent background used to be.

The practical rule: if the source image has meaningful transparency, whether it is a logo, an icon, or a product cutout, keep the output format in PNG, GIF, or BMP. Save JPG conversion for photographs and anything else where a solid background was never the point.

A Working Example, Verified Against the Live Sample

This is adapted directly from PDF4me's own Python sample in pdf4me-api-samples (Image/Convert Image Format/Python/), with the field names and both response paths confirmed against that source rather than assumed:

import os
import base64
import time
import requests

API_KEY = "your-api-key-here"
BASE_URL = "https://api.pdf4me.com"

def convert_image_format(image_path, output_path, current_format, new_format):
    with open(image_path, "rb") as f:
        image_base64 = base64.b64encode(f.read()).decode("utf-8")

    payload = {
        "docName": os.path.basename(image_path),
        "docContent": image_base64,
        "currentImageFormat": current_format,   # e.g. "PNG"
        "newImageFormat": new_format,            # e.g. "GIF"
        "isAsync": True
    }

    headers = {
        "Authorization": f"Basic {API_KEY}",
        "Content-Type": "application/json"
    }

    url = f"{BASE_URL}/api/v2/ConvertImageFormat"
    response = requests.post(url, json=payload, headers=headers)

    if response.status_code == 200:
        # Synchronous success: body is the raw converted image
        with open(output_path, "wb") as f:
            f.write(response.content)
        print(f"Done (sync): {output_path}")
        return

    if response.status_code == 202:
        # Accepted for async processing: poll the Location URL
        poll_url = response.headers.get("Location")
        if not poll_url:
            raise RuntimeError("202 response had no Location header to poll")

        for attempt in range(10):
            time.sleep(10)
            poll_response = requests.get(poll_url, headers=headers)

            if poll_response.status_code == 200:
                with open(output_path, "wb") as f:
                    f.write(poll_response.content)
                print(f"Done (async, attempt {attempt + 1}): {output_path}")
                return
            elif poll_response.status_code == 202:
                continue
            else:
                raise RuntimeError(f"Polling failed: {poll_response.status_code} - {poll_response.text}")

        raise TimeoutError("Conversion did not complete after 10 polling attempts")

    raise RuntimeError(f"Request failed: {response.status_code} - {response.text}")


if __name__ == "__main__":
    convert_image_format("logo.png", "logo.gif", "PNG", "GIF")
Enter fullscreen mode Exit fullscreen mode

Note the format choice in that last line: PNG to GIF, not PNG to JPG, specifically because it preserves whatever transparency the source logo has. Swap new_format to "JPG" and you will get a valid file back, just not one with the same transparent background it started with.

PDF4me also publishes working samples in C#, Java, JavaScript, Salesforce, Google Apps Script, and AWS Lambda alongside the Python one, so you are rarely starting from a blank file whatever your stack.

Doing the Same Thing Without Writing Code

Not every team wants a Python service babysitting image conversions. If your pipeline already lives in a no-code or low-code automation tool, the same endpoint is available as a native action:

Zapier: the Convert Image Format action drops into any Zap, so a new file landing in a connected app (Dropbox, Google Drive, an email attachment) can trigger a format conversion automatically before it ever reaches a human.

Make: the Convert Image Format module does the same inside a Make scenario, useful when the conversion is one step in a longer visual workflow rather than a standalone task.

n8n: the Convert Image Format node accepts binary data, Base64 strings, or public URLs as input, and returns file metadata (size, confirmed format, MIME type) alongside the converted file, which matters if a later node in your workflow needs to branch on the result.

Power Automate: the Convert Image Format action integrates directly with SharePoint, OneDrive, and email-based flows, which covers a lot of ground for teams already living inside Microsoft 365 rather than a separate automation tool.

All four wrap the same underlying REST endpoint, so the format-support list and the transparency behavior described above apply identically no matter which one you pick.

If you would rather test the endpoint's behavior on a real file before wiring it into any of the above, the Interactive API Tester lets you run a live request from the browser and see the exact response shape before you write a line of integration code.

Where This Fits in a Bigger Pipeline

Format conversion is rarely the whole job. It is usually one link in a chain: a scanned document comes in as TIFF, gets converted to PNG for web display, gets watermarked, gets compressed, gets attached to an outgoing email. Or a batch of vendor logos arrives in six different formats and needs to standardize to one before they can be dropped into a template engine.

Treating conversion as its own composable step, callable from whatever orchestration tool you already use, is what makes that kind of pipeline maintainable. You are not writing custom decode logic for six formats; you are making one API call with two format parameters (correctly cased, now) and trusting it to handle the rest, transparency caveat and async response included.

Before you wire this into production, it is worth reading through the general connecting to the PDF4me API guide if you have not already authenticated against the platform, since every request here, REST or through any of the four automation tools above, needs a valid API key from the developer dashboard.

Website: pdf4me.com
Documentation: docs.pdf4me.com