Stored files
With StoreFile=true you get a download link instead of base64 data. The link works for 3 hours, and a cleanup job deletes the file within 15 minutes after that.
Contents (7 sections)
By default the converted file comes back inside the JSON response and we keep no copy: the conversion runs in memory, or in a temporary folder that is deleted when it ends. Send StoreFile=true and we keep each output file instead, behind a download link that works for 3 hours.
A link helps when something other than the code that sent the request should fetch the file, such as a queue worker, another service, or the one person you send the link to. If your code only needs the bytes, the default is simpler, and nothing is stored.
Ask for a link
Add StoreFile=true to any conversion. true, 1, yes and on turn it on and false, 0, no and off leave it off, in upper or lower case. Any other value gets a 400. This converts a two-page PDF to JPG images and stores both:
curl -sS -X POST https://api.pdfconversionapi.com/v2/convert/pdf/to/jpg \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@season-guide.pdf \
-F ImageResolution=72 \
-F StoreFile=true \
-o response.jsonresponse.json from our test run, with only the host in Url changed:
{
"ConversionCost": 1,
"ConversionTime": 0.04,
"Files": [
{
"FileName": "season-guide-1.jpg",
"FileExt": "jpg",
"FileSize": 65272,
"Url": "https://api.pdfconversionapi.com/v2/d/32001ff088d5c35e10e4bc6f6ddf09a9/season-guide-1.jpg",
"FileId": "32001ff088d5c35e10e4bc6f6ddf09a9"
},
{
"FileName": "season-guide-2.jpg",
"FileExt": "jpg",
"FileSize": 66627,
"Url": "https://api.pdfconversionapi.com/v2/d/ab078ee0cb902b5d6837a3fa90504d1c/season-guide-2.jpg",
"FileId": "ab078ee0cb902b5d6837a3fa90504d1c"
}
]
}FileDatais gone;UrlandFileIdtake its place. The other fields are the same as in a normal response.- Each output file is stored on its own, with its own
FileId. Two pages give two ids, two links, and two deletes if you want both gone early. FileIdis 32 hex characters: 128 random bits, not derived from your account, your file or the time.Urlishttps://api.pdfconversionapi.com/v2/d/{FileId}/{FileName}, with the name percent-encoded (a space becomes%20). Use it as it comes; don't build it yourself.- The request is still synchronous, and it still counts as one conversion.
StoreFilechanges where the result goes, not when it is ready. - Only the output is stored. The file you uploaded is never kept.
Download the file
A download link needs no API key. Anyone who has the link can download the file until it expires or you delete it, so treat it like a password: give it only to whoever should get the file, and keep it out of public pages and logs other people read.
curl -sS --fail -o season-guide-1.jpg \
"https://api.pdfconversionapi.com/v2/d/32001ff088d5c35e10e4bc6f6ddf09a9/season-guide-1.jpg"- The link answers
GETonly. AHEADrequest gets a 404. - Downloading doesn't use up the link. It works as many times as you like until it expires.
- The name at the end of the link must match
FileNameexactly; any other name gets a 404. - Every download returns the whole file with a 200. Range requests (resuming a partial download) aren't supported.
- Downloads don't count toward your rate limit.
Download headers
A download carries these headers:
| Header | Value | Why |
|---|---|---|
Content-Type | Set from the extension, see below | So your HTTP client and the browser know the type. |
Content-Length | The size in bytes, the same as FileSize | |
Content-Disposition | attachment; filename*=UTF-8''season-guide-1.jpg | A browser saves the file under its name instead of opening it. |
Content-Security-Policy | sandbox | If an HTML or SVG result is opened in a browser anyway, its scripts don't run. |
X-Content-Type-Options | nosniff | Browsers don't guess a different type from the content. |
Cache-Control | private, no-store | No cache, shared or in the browser, keeps a copy. |
x-request-id | A random id for the download | Send it with any question about a failed download (Errors). |
Content-Type is set for these extensions; every other type is sent as application/octet-stream:
| Extension | Content-Type |
|---|---|
pdf | application/pdf |
png, jpg, gif, webp, tiff | image/png, image/jpeg, image/gif, image/webp, image/tiff |
svg | image/svg+xml |
txt, csv | text/plain; charset=utf-8, text/csv; charset=utf-8 |
json | application/json |
zip | application/zip |
docx, xlsx, pptx | The Office Open XML types, such as application/vnd.openxmlformats-officedocument.wordprocessingml.document |
Because of attachment, opening a link in a browser tab or frame downloads the file instead of displaying it. To show a PDF inside your app, fetch it on your server and serve it from there with the headers you choose. The API also sends no CORS headers, so JavaScript on another site can't read a link with fetch(); a plain link that the user clicks still downloads the file.
How long a stored file lasts
| When | What happens |
|---|---|
| The response arrives | The file is already stored in object storage, in a bucket created in the US jurisdiction. The link works. |
| 3 hours later (10,800 seconds) | The link stops working and answers 404 not found. |
| Within the next 15 minutes | A cleanup job, which runs every 15 minutes, deletes every file whose link has expired. |
| 1 day after the file was stored | A lifecycle rule on the storage bucket deletes anything still there. It is a backstop in case the cleanup job stops running. |
The 3 hours count from when the file was stored, not from the last download. The lifecycle rule only matters if the cleanup job fails: our cloud provider applies lifecycle rules typically within 24 hours of the expiry, so in that case a file could last up to about 48 hours.
Security and data handling covers storage location, encryption at rest and who can read stored files.
Delete a file early
DELETE /v2/d/{FileId} removes a stored file at once. It needs the API key of the account that created the file:
curl -sS -X DELETE -o /dev/null -w '%{http_code}\n' \
https://api.pdfconversionapi.com/v2/d/32001ff088d5c35e10e4bc6f6ddf09a9 \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY"| Status | Meaning |
|---|---|
| 204 | Deleted. The response has no body, and the link now answers 404. |
| 404 | Nothing to delete: the id is unknown, the file was already deleted or cleaned up, or it belongs to another account. The answer is the same in each case, so a 404 doesn't reveal whether an id exists. |
| 401 | The key is missing or unknown, or it was replaced or its account deleted. |
A delete removes one file, the one with that FileId. For a conversion that returned several files, send one DELETE per FileId. Deletes don't count toward your rate limit.
Store, download and delete
The three examples below do the whole round trip for the two-page PDF above: they convert it with StoreFile=true, download every file through its link, and delete each stored copy as soon as it is on disk. Download our sample PDF (118 KB) and save it as season-guide.pdf to try them.
#!/bin/sh
# Needs curl 7.76 or later and jq. Save it as store.sh and run: sh store.sh
set -eu
API=https://api.pdfconversionapi.com
curl -sS --fail-with-body -X POST "$API/v2/convert/pdf/to/jpg" \
-H "Authorization: Bearer $PDFCONVERSIONAPI_KEY" \
-F File=@season-guide.pdf \
-F ImageResolution=72 \
-F StoreFile=true \
-o response.json
count=$(jq '.Files | length' response.json)
i=0
while [ "$i" -lt "$count" ]; do
name=$(jq -r ".Files[$i].FileName" response.json)
url=$(jq -r ".Files[$i].Url" response.json)
id=$(jq -r ".Files[$i].FileId" response.json)
curl -sS --fail -o "$name" "$url"
curl -sS --fail -X DELETE "$API/v2/d/$id" -H "Authorization: Bearer $PDFCONVERSIONAPI_KEY"
echo "downloaded $name, deleted the stored copy"
i=$((i + 1))
done# Needs the requests package: pip install requests
import os
import requests
API = "https://api.pdfconversionapi.com"
headers = {"Authorization": f"Bearer {os.environ['PDFCONVERSIONAPI_KEY']}"}
with open("season-guide.pdf", "rb") as f:
response = requests.post(
f"{API}/v2/convert/pdf/to/jpg",
headers=headers,
files={"File": f},
data={"ImageResolution": "72", "StoreFile": "true"},
timeout=240,
)
result = response.json()
if not response.ok:
raise SystemExit(f"{result['Code']}: {result['Message']}")
for file in result["Files"]:
# The link needs no key. Stream the download to disk instead of holding it in memory.
with requests.get(file["Url"], stream=True, timeout=60) as download:
download.raise_for_status()
with open(file["FileName"], "wb") as out:
for chunk in download.iter_content(chunk_size=64 * 1024):
out.write(chunk)
deleted = requests.delete(f"{API}/v2/d/{file['FileId']}", headers=headers, timeout=30)
deleted.raise_for_status()
print(file["FileName"], os.path.getsize(file["FileName"]), "bytes; delete returned", deleted.status_code)// Needs Node.js 20 or later. Save it as store.mjs and run: node store.mjs
import { createWriteStream } from "node:fs";
import { readFile, stat } from "node:fs/promises";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const API = "https://api.pdfconversionapi.com";
const auth = { Authorization: `Bearer ${process.env.PDFCONVERSIONAPI_KEY}` };
const form = new FormData();
form.append("File", new Blob([await readFile("season-guide.pdf")]), "season-guide.pdf");
form.append("ImageResolution", "72");
form.append("StoreFile", "true");
const response = await fetch(`${API}/v2/convert/pdf/to/jpg`, { method: "POST", headers: auth, body: form });
const result = await response.json();
if (!response.ok) throw new Error(`${result.Code}: ${result.Message}`);
for (const file of result.Files) {
// The link needs no key. Stream the download to disk instead of holding it in memory.
const download = await fetch(file.Url);
if (!download.ok) throw new Error(`${file.FileName}: download returned ${download.status}`);
await pipeline(Readable.fromWeb(download.body), createWriteStream(file.FileName));
const deleted = await fetch(`${API}/v2/d/${file.FileId}`, { method: "DELETE", headers: auth });
if (deleted.status !== 204) throw new Error(`${file.FileName}: delete returned ${deleted.status}`);
console.log(file.FileName, (await stat(file.FileName)).size, "bytes; delete returned", deleted.status);
}The Python and Node.js versions print:
season-guide-1.jpg 65272 bytes; delete returned 204
season-guide-2.jpg 66627 bytes; delete returned 204We ran all three on 2026-11-03 against the API server running locally in a container, built the same way as the hosted service, with only the base URL changed. Versions: curl 8.7.1, jq 1.8.1, Python 3.14.2 with requests 2.34.2, Node.js 24.15.0. Locally, that server stores files on its own disk; on the hosted API, the gateway stores them in object storage as described above, behind the same Url, FileId and DELETE interface. On 2026-11-04 a change to how pages are drawn changed the two JPGs; the sizes above are from the same conversion, with the same options, run that day.
What a stored file can't do yet
- Be the input of another conversion. A
FileIdcan't be passed to a later request; download the file and upload it again. - Last longer than 3 hours. The lifetime is the same for every account.
- Be stored in your own bucket. Results go to our storage bucket only.