
PDF4meSomewhere 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.
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
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.
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:
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.
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.
response.content straight to a file.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.
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.
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")
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.
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.
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