Create ZIP files API
POST /v2/convert/file/to/zip packs every uploaded file into one ZIP archive. The sample sent an itinerary, a CSV gear list and a PNG map (1,985 bytes together) with CompressionLevel=Optimal and FileName=larch-lake-trip, and got back larch-lake-trip.zip, 2,204 bytes.
- Endpoint
- POST /v2/
convert/ file/ to/ zip - Accepts
- file
- Sample timing
- under 1 ms median of 5 runsHow we measure
- Request size
- Up to 10 MB on the Free plan or 100 MB on paid plans, all files together
Request and response
Every sample below ran against the API before this page was published. Set PDFCONVERSIONAPI_KEY to your key and run it as is.
curl -sS -X POST \
https://api.pdfconversionapi.com/v2/convert/file/to/zip \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F Files=@trip-itinerary.txt \
-F Files=@gear-list.csv \
-F Files=@campsite-map.png \
--form-string CompressionLevel=Optimal \
--form-string FileName=larch-lake-trip \
-o response.json
# FileData is base64. Save the first file in converted/,
# or print the JSON error.
if jq -e '.Files' response.json > /dev/null; then
mkdir -p converted
name=$(jq -r '.Files[0].FileName' response.json)
jq -r '.Files[0].FileData' response.json \
| base64 --decode > "converted/$name"
else
cat response.json
fiimport base64
import os
import requests
key = os.environ["PDFCONVERSIONAPI_KEY"]
response = requests.post(
"https://api.pdfconversionapi.com/v2/convert/file/to/zip",
headers={"Authorization": f"Bearer {key}"},
files=[
("Files", open("trip-itinerary.txt", "rb")),
("Files", open("gear-list.csv", "rb")),
("Files", open("campsite-map.png", "rb")),
],
data={
"CompressionLevel": "Optimal",
"FileName": "larch-lake-trip",
},
timeout=180,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
os.makedirs("converted", exist_ok=True)
for file in result["Files"]:
path = os.path.join("converted", file["FileName"])
with open(path, "wb") as out:
out.write(base64.b64decode(file["FileData"]))
print(path, file["FileSize"], "bytes")
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
const form = new FormData();
for (const file of [
"trip-itinerary.txt",
"gear-list.csv",
"campsite-map.png",
]) {
form.append("Files", new Blob([await readFile(file)]), file);
}
form.append("CompressionLevel", "Optimal");
form.append("FileName", "larch-lake-trip");
const url = "https://api.pdfconversionapi.com/v2/convert/file/to/zip";
const key = process.env.PDFCONVERSIONAPI_KEY;
const response = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${key}` },
body: form,
});
const result = await response.json();
if (!response.ok) {
throw new Error(`${result.Code}: ${result.Message}`);
}
await mkdir("converted", { recursive: true });
for (const file of result.Files) {
const path = join("converted", file.FileName);
await writeFile(path, Buffer.from(file.FileData, "base64"));
console.log(path, file.FileSize, "bytes");
}
{
"ConversionCost": 1,
"ConversionTime": 0,
"Files": [
{
"FileName": "larch-lake-trip.zip",
"FileExt": "zip",
"FileSize": 2204,
"FileData": "UEsDBC0AAAAIAAAAIQCgPYuK…"
}
]
}Response from our test run on 2026-11-03. ConversionTime shows the median of 5 runs. FileData is shortened here; the real response carries the whole file as base64.
The sample, converted
The same file the code sends. Download the input and the output and compare them yourself.
- Input
- trip-itinerary.txt (530 bytes)gear-list.csv (153 bytes)campsite-map.png (1.3 KB)
- Output
- larch-lake-trip.zip (2.2 KB)
- Source
- Three files for a fictional overnight hiking trip, made for this site: a text itinerary, a CSV gear list and a drawn campsite map. (CC0-1.0). How it was made
- Made by
- 2026-11-04, API build
9eeeb0640aba
Parameters
Send these as form fields next to the file. Names are case-insensitive. If you set one of the documented options below that we don't support yet, the request fails with a 400 that names it instead of returning a file made without it.
| Parameter | What it does |
|---|---|
CompressionLevelenum, default Medium | Deflate level by name or number: NoCompression stores the files (level 0), the next name is level 1, Medium is 6 and Optimal 9. For the sample's three files, in that order: 2,449, 2,324, 2,198 and 2,204 bytes.Values: NoCompression, Fastest, Medium, Optimal, or 0–9 |
FileNamestring | Name of the archive. Without it the ZIP is named after the first file, so the sample would be trip-itinerary.zip. |
StoreFileboolean, default false | true keeps the ZIP for 3 hours and returns Files[].Url instead of the archive in base64. |
Not supported yet
Setting one of these to anything but its default returns 400.
| Option | What to do instead |
|---|---|
Password | Encrypted archives can't be made yet. Password returns 400 "Password is not supported yet for file/to/zip", whether you send one file or several; an empty Password is ignored. |
What carries over, and what doesn't
Kept
- Each file's bytes and its upload name, in upload order
- Extensions as sent:
trip-itinerary.txt,gear-list.csv,campsite-map.png - Text compression: the 530-byte itinerary is 310 bytes inside the sample ZIP and the CSV 123 of 153
- A plain flat archive that standard unzip tools list without errors; each entry has
rw-r--r--permissions
Lost or not yet supported
- Folders: upload names are cut to their last part, so
../../etc/passwdwas stored aspasswd.file. - Names without an extension get
.fileadded (LICENSEbecameLICENSE.file). - Original dates: every entry is dated 1980-01-01 00:00.
- Room on already-compressed files: the 1,302-byte PNG takes 1,307 bytes, since it is deflated even when that doesn't help.
Limits
- 1 to 50 files per request; a 51st returns 400 "at most 50 files per request" and none returns 400 "no input files were provided".
- Requests up to 10 MB on the Free plan or 100 MB on paid plans, all files and fields together.
- 120 seconds per request, then 504.
Where teams use it
One download for many results
After converting a batch of documents, pack the results into one archive so the user downloads one file. Each result keeps its own name inside the ZIP.
Bundles for email or upload limits
Systems that accept one attachment per message, or one file per form field, can take a ZIP of the whole set: the sample bundles a text itinerary, a spreadsheet and a map.
Questions
Which compression level should I use?
For the sample, Medium, the default, gave the smallest archive: Optimal came out 6 bytes larger (2,204 against 2,198) and level 1 was 126 bytes larger. For files that are already compressed, such as PNG, JPEG or DOCX, NoCompression skips the deflate step at a small cost in size: 2,449 bytes for the sample.
What happens if two files have the same name?
The second is renamed with -2 before its extension, and names are compared without regard to case. Uploading a.txt and A.txt gave a.txt and A-2.txt in the archive.
Can I keep a folder structure inside the ZIP?
No. Only the last part of each upload name is used, so every file sits at the top of the archive. If you need folders, build the archive in your own code.
Do the entries need ZIP64 support to open?
Every entry is written with a ZIP64 size field and "version needed to extract" 4.5, even for small files. The unzip tools we listed the sample with read it without complaint; a tool without ZIP64 support may not.
Convert your first FILE file in a minute.