API Reference Private beta
PrivConvert API
A single REST endpoint for every conversion - images, documents, data, eBooks, fonts and archives. Files are processed entirely in memory and deleted the instant they are returned. Nothing is ever written to disk.
Introduction
The PrivConvert API turns any file conversion on PrivConvert into a single authenticated POST request. It is designed for teams that handle sensitive documents - legal, medical, finance - and cannot send files to a service that stores them.
https://privconvert.com/apiHTTPS only (TLS 1.3)Bearer API keymultipart/form-dataBinary file streamAuthentication
Every request must include your secret API key as a Bearer token in the Authorization header. Keys are issued per account and look like pk_live_…. Keep them server-side - never expose a key in client-side code.
# Pass your key in the Authorization header
Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxx Quick start
Convert a Word document to PDF. The converted file streams straight back in the response body.
# Convert contract.docx → PDF
curl -X POST https://privconvert.com/api/convert/word-to-pdf \
-H "Authorization: Bearer pk_live_…" \
-F "[email protected]" \
-o contract.pdf Convert a file
POST /api/convert/{tool}
The {tool} path segment selects the conversion - for example word-to-pdf, png-to-jpg or json-to-csv. Send the source file as a file field in a multipart/form-data body. The same pattern works for all 450+ tools.
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
file | file | Yes | The source file to convert, sent as multipart form data. |
quality | integer | No | Output quality 1-100 for image/PDF tools. The default varies by format - 90 for JPEG, 85 for WebP, 60 for AVIF. GET /api/tools reports the exact default for each tool. |
width | integer | No | Target width in pixels for resize-capable image tools. |
height | integer | No | Target height in pixels for resize-capable image tools. |
Response
On success (200) the response body is the converted file - a raw binary stream, not JSON. Read it from the body and write it to disk. Useful headers:
| Header | Example |
|---|---|
Content-Type | application/pdf |
Content-Disposition | attachment; filename="contract.pdf" |
X-Credits-Remaining | 4985 |
X-Sanitize-Report | {"javascript":2,"launch":1,"clean":false} |
X-Sanitize-Report is returned by sanitize-pdf and sanitize-svg only. It is a JSON object counting what was removed from the file, so a pipeline can log which uploads arrived carrying active content without keeping the original. "clean": true means nothing was found.
Errors return JSON instead of a file:
{
"error": {
"code": "file_too_large",
"message": "File exceeds your plan’s size limit."
}
}
Convert many files
POST /api/convert-batch runs one tool over up to 20 files in a single request and returns them as a ZIP. Same key, same tool names as the single-file endpoint.
curl -X POST https://privconvert.com/api/convert-batch \
-H "Authorization: Bearer pk_live_..." \
-F "tool=png-to-jpg" \
-F "[email protected]" -F "[email protected]" -F "[email protected]" \
-o converted.zip | Field | Required | Notes |
|---|---|---|
tool | Yes | Tool id, e.g. png-to-jpg. An unknown id returns 400. |
files | Yes | Repeat the field once per file. More than 20 returns 400. |
quality | No | Applies to every file in the request. Defaults to 85. |
start, end | No | Page range, for tools that take one. Applied to each file individually. |
How it counts against your plan
This is the reason to use it. A batch is one request against your per-minute rate limit regardless of how many files it carries, while each converted file counts as one conversion against your monthly quota. Twenty files sent as a batch cost one of your rate-limit slots; sent individually they cost twenty. Either way the quota cost is identical, so batching buys you throughput, not cheaper conversions.
The whole request must fit in 100 MB, and your plan's per-request size cap applies to the total as well. Over either limit the response is 413.
When some files fail
One unreadable file does not fail the batch. Everything that converted is still returned, and the ZIP gains a PRIVCONVERT-ERRORS.txt listing what was skipped and why. The status is still 200, so check the headers rather than the status code:
| Header | Example | Meaning |
|---|---|---|
X-Converted-Count | 18 | Files in the ZIP. |
X-Failed-Count | 2 | Files skipped. |
X-Partial-Success | 1 | Present only when something was skipped. |
If nothing converted there is no partial result worth shipping, so the response is 400 with the first failure reason instead of an empty archive.
A single file above your plan's size cap belongs on the chunked upload endpoint instead - batch is for many ordinary files, not for one large one.
Code examples
The same request in your language of choice. Swap the png-to-jpg slug for any tool.
curl -X POST https://privconvert.com/api/convert/png-to-jpg \
-H "Authorization: Bearer pk_live_…" \
-F "[email protected]" \
-o photo.jpg import requests
with open("photo.png", "rb") as f:
r = requests.post(
"https://privconvert.com/api/convert/png-to-jpg",
headers={"Authorization": "Bearer pk_live_…"},
files={"file": f},
)
r.raise_for_status()
with open("photo.jpg", "wb") as out:
out.write(r.content) import { readFile, writeFile } from "node:fs/promises";
const form = new FormData();
form.append("file", new Blob([await readFile("photo.png")]), "photo.png");
const res = await fetch("https://privconvert.com/api/convert/png-to-jpg", {
method: "POST",
headers: { Authorization: "Bearer pk_live_…" },
body: form,
});
if (!res.ok) throw new Error("Conversion failed: " + res.status);
await writeFile("photo.jpg", Buffer.from(await res.arrayBuffer())); <?php
$ch = curl_init("https://privconvert.com/api/convert/png-to-jpg");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer pk_live_…"],
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => ["file" => new CURLFile("photo.png")],
]);
file_put_contents("photo.jpg", curl_exec($ch));
curl_close($ch); // Uses OkHttp
OkHttpClient client = new OkHttpClient();
RequestBody body = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", "photo.png",
RequestBody.create(new File("photo.png"), MediaType.parse("image/png")))
.build();
Request request = new Request.Builder()
.url("https://privconvert.com/api/convert/png-to-jpg")
.header("Authorization", "Bearer pk_live_…")
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
Files.write(Paths.get("photo.jpg"), response.body().bytes());
} using var client = new HttpClient();
using var form = new MultipartFormDataContent();
var file = new ByteArrayContent(await File.ReadAllBytesAsync("photo.png"));
file.Headers.ContentType = new MediaTypeHeaderValue("image/png");
form.Add(file, "file", "photo.png");
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "pk_live_…");
var res = await client.PostAsync(
"https://privconvert.com/api/convert/png-to-jpg", form);
res.EnsureSuccessStatusCode();
await File.WriteAllBytesAsync("photo.jpg", await res.Content.ReadAsByteArrayAsync()); file, _ := os.Open("photo.png")
defer file.Close()
var body bytes.Buffer
w := multipart.NewWriter(&body)
part, _ := w.CreateFormFile("file", "photo.png")
io.Copy(part, file)
w.Close()
req, _ := http.NewRequest("POST",
"https://privconvert.com/api/convert/png-to-jpg", &body)
req.Header.Set("Authorization", "Bearer pk_live_…")
req.Header.Set("Content-Type", w.FormDataContentType())
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
out, _ := os.Create("photo.jpg")
io.Copy(out, res.Body) Large file uploads
A single POST to /api/convert/{tool} is capped at 100 MB by our edge network. To convert larger files - up to your plan’s max file size (500 MB above 10,000 conversions a month) - upload the file in parts of under 100 MB each. It is a simple three-step flow, authenticated with the same Bearer key:
- Initialise the session - declare the total size, number of parts, filename and tool.
- Upload each part in order (index
0…N-1), each under 100 MB. - Convert - the server reassembles the file in memory, converts it, and streams the result back. It counts as one conversion against your quota.
POST in Quick start is simpler. Upload sessions are bound to your account and expire after 5 minutes of inactivity.
POST /api/upload-init · POST /api/upload-chunk · POST /api/convert-chunked
| Step | Endpoint | Form fields | Returns |
|---|---|---|---|
| 1 | /api/upload-init | total_size, total_chunks, filename, tool | { "upload_id": "…" } |
| 2 | /api/upload-chunk | upload_id, chunk_index, file (the chunk) | { "received": n, "total": N } |
| 3 | /api/convert-chunked | upload_id (+ optional quality, width, height, password) | Converted file (binary stream) |
# Convert a 300 MB PDF by uploading it in parts (bash).
# Requires a key above 10,000/mo. Splits, uploads, then converts.
KEY="pk_live_…"
FILE=big.pdf
TOOL=compress-pdf
CHUNK=$((95*1024*1024))
# 0. split into 95 MB parts
split -b $CHUNK -d "$FILE" part_
N=$(ls part_* | wc -l)
SIZE=$(stat -c%s "$FILE")
# 1. init
ID=$(curl -s https://privconvert.com/api/upload-init \
-H "Authorization: Bearer $KEY" \
-F total_size=$SIZE -F total_chunks=$N -F filename="$FILE" -F tool=$TOOL \
| jq -r .upload_id)
# 2. upload each chunk
i=0; for p in part_*; do
curl -s https://privconvert.com/api/upload-chunk \
-H "Authorization: Bearer $KEY" \
-F upload_id=$ID -F chunk_index=$i -F file=@"$p" > /dev/null
i=$((i+1))
done
# 3. convert + download
curl -s https://privconvert.com/api/convert-chunked \
-H "Authorization: Bearer $KEY" \
-F upload_id=$ID -o out.pdf
# Python - same three steps.
import math, requests
KEY, FILE, TOOL = "pk_live_…", "big.pdf", "compress-pdf"
CHUNK = 95 * 1024 * 1024
H = {"Authorization": f"Bearer {KEY}"}
B = "https://privconvert.com/api"
data = open(FILE, "rb").read()
chunks = [data[i:i+CHUNK] for i in range(0, len(data), CHUNK)]
uid = requests.post(f"{B}/upload-init", headers=H, data={
"total_size": len(data), "total_chunks": len(chunks),
"filename": FILE, "tool": TOOL,
}).json()["upload_id"]
for i, c in enumerate(chunks):
requests.post(f"{B}/upload-chunk", headers=H,
data={"upload_id": uid, "chunk_index": i},
files={"file": c})
r = requests.post(f"{B}/convert-chunked", headers=H,
data={"upload_id": uid})
open("out.pdf", "wb").write(r.content)
Available tools
Every server-side conversion uses the same /api/convert/{tool} pattern. A sample of the 450+ available slugs:
Images
png-to-jpgto-webpheic-to-jpgcompress-imageresize-image Documents
word-to-pdfexcel-to-pdfpdf-to-wordhtml-to-pdfmarkdown-to-pdf Data
json-to-csvcsv-to-jsonxml-to-jsonyaml-to-jsonjson-to-sql eBooks
epub-to-mobipdf-to-epubmobi-to-epubazw3-to-epub Fonts & archives
ttf-to-woff2otf-to-woff2rar-to-zip7z-to-zip See the full tool catalog for every supported conversion. If a tool exists on the site, it works over the API with the same slug.
List tools programmatically
GET /api/tools
A public, machine-readable catalog of every conversion the API exposes - ideal for building dynamic integrations without hard-coding slugs. No API key required. Filter with ?category= (e.g. image, document, data, audio, ebook, archive, mesh, font, spreadsheet, presentation, medical). Every response also carries the full categories list, so an integration can discover them rather than hard-code this one.
curl https://privconvert.com/api/tools?category=image {
"version": "1",
"base_url": "https://privconvert.com/api",
"count": 79,
"categories": ["archive", "audio", "data", "…"],
"tools": [
{
"id": "png-to-jpg",
"name": "PNG to JPG",
"category": "image",
"input": "png",
"output": "jpg",
"endpoint": "/api/convert/png-to-jpg",
"params": [{ "name": "quality", "type": "integer", "default": 90 }]
}
]
} Rate limits & quotas
Quota is counted per successful conversion and resets on the first of each month. Beyond your rate limit or quota, requests return 429 with a Retry-After header.
| Monthly volume | Rate limit | Concurrent jobs | Max request size |
|---|---|---|---|
| Up to 10,000 / mo | 60 req / min | 2 | 250 MB |
| 10,001 - 50,000 / mo | 120 req / min | 4 | 500 MB |
| Above 50,000 / mo | 300 req / min | 6 | 500 MB |
| Enterprise | Custom | Custom | Custom |
Rate limit and quota are shared across every key on your account, so rotating keys does not reset either. The three limits fail differently and are worth telling apart: exceeding the per-minute rate or the monthly quota returns 429 rate_limited, while exceeding concurrent jobs - conversions running at the same instant, not per minute - returns 429 concurrency_limited. The second one is not fixed by waiting longer between batches; it is fixed by running fewer requests in parallel.
Errors
The API uses conventional HTTP status codes. 2xx means success; 4xx means a problem with the request; 5xx means a problem on our side.
| Status | Code | Meaning |
|---|---|---|
200 | OK | Conversion succeeded. The body is the converted file. |
400 | bad_request | Malformed request - missing file, bad parameter, or unreadable input. |
401 | unauthorized | Missing or invalid API key. |
411 | length_required | No Content-Length header. Size limits are enforced before the body is read, so a declared length is mandatory. Every standard HTTP client sends one. |
413 | file_too_large | The uploaded file exceeds your plan’s size limit. |
415 | unsupported_type | The input file type is not valid for this tool. |
422 | conversion_failed | The file was read but could not be converted. |
429 | rate_limited | Per-minute rate or monthly quota exceeded. Slow down and retry after Retry-After. |
429 | concurrency_limited | Too many conversions running at once on your account. Reduce parallelism, then retry after Retry-After. |
500 | server_error | Unexpected error on our side. Safe to retry. |
503 | server_busy | The service is at capacity right now. Retry after Retry-After with backoff. |
Privacy & data handling
Privacy is the product. Every file you send to the API is:
- Processed entirely in RAM - never written to disk.
- Deleted from memory the instant the converted file is returned.
- Never logged, never used for training, never shared.
- Covered by a signed DPA on Enterprise plans.
Request a beta key and we will get you converting in minutes.