File conversion is a solved problem in the sense that the tools exist — FFmpeg, LibreOffice, Calibre, dcraw. The unsolved part is hosting them reliably, keeping them updated, handling concurrency, and not letting a malformed user file crash your server. If your product's core feature isn't file conversion, you probably shouldn't be in the business of running LibreOffice headless in a container. The ChangeThisFile API is the alternative.
What developers actually need file conversion for
File conversion shows up in unexpected places across typical product backlogs:
- User upload normalization — users upload HEIC, TIFF, HEVC, or DOCX; your app needs JPEG, PNG, MP4, or PDF
- Preview generation — convert DOCX to PDF for an inline viewer, or video to a GIF thumbnail
- Export pipelines — users want their data as CSV, their presentation as PDF, their audio as MP3
- Storage optimization — convert uploaded images to WebP before storing in S3
- Data ingestion — normalize CSV/TSV/XLSX to JSON for downstream processing
- Webhook + async pipelines — long video transcoding jobs that complete in the background
In each case, the work is: accept the file, convert it, return or store the result. The ChangeThisFile API handles the middle step so your code can focus on the first and last.
Common format pairs in developer workflows
These are the routes developers hit most often:
- DOCX → PDF — generate PDFs from user-uploaded or templated Word documents
- HEIC → JPG — normalize iPhone photos before display or storage
- PNG / JPG → WebP — compress images before pushing to CDN
- MP4 → GIF / WebM — generate animated previews, loop videos for web embeds
- XLSX → CSV / JSON — ingest spreadsheet uploads from non-technical users
- PDF → JPG / PNG — generate previews for document viewers
- WAV → MP3 — compress audio before storage or delivery
- SVG → PNG — render vector assets for OG images or thumbnails
- EPUB → PDF — export ebook content for print or LMS upload
The full route table has 690 pairs. Check the converter to confirm your specific pair before building.
The browser tool: useful for testing and one-off conversions
Before writing any API code, test your conversion at changethisfile.com. Drop in a file, pick a target format, download the result. No signup. No upload for image/data/font routes — those run in the browser with your files staying local.
The browser tool is enough when:
- You're validating whether a specific format pair works as expected
- You need a quick one-off conversion during development
- You're checking output quality (DPI, compression, fidelity) before deciding on parameters
It's not enough when you're building a feature. That's when the API comes in.
API integration: when you're building a feature
The V1 API is at POST https://changethisfile.com/v1/convert. All requests are authenticated with a Bearer token. Get a free key — no card required.
Synchronous conversion (most routes)
curl -X POST https://changethisfile.com/v1/convert \
-H "Authorization: Bearer ctf_sk_your_key" \
-F "file=@photo.heic" \
-F "target=jpg" \
--output photo.jpg
Idempotency keys
For operations you might retry (network failures, timeouts), pass Idempotency-Key in the header. The API returns the same result for duplicate requests within a 24-hour window — safe to retry without double-billing.
curl -X POST https://changethisfile.com/v1/convert \
-H "Authorization: Bearer ctf_sk_your_key" \
-H "Idempotency-Key: convert-user-123-upload-456" \
-F "file=@document.docx" \
-F "target=pdf" \
--output document.pdf
Async jobs + webhooks
Long conversions (video transcoding, large documents) support async mode. Submit the job and receive a webhook when it's done.
# Submit async job
curl -X POST https://changethisfile.com/v1/convert \
-H "Authorization: Bearer ctf_sk_your_key" \
-F "file=@lecture.mp4" \
-F "target=webm" \
-F "async=true" \
-F "webhook_url=https://yourapp.com/webhooks/ctf"
# Response: {"job_id": "job_abc123", "status": "queued"}
Webhook verification (HMAC)
Webhooks are signed with HMAC-SHA256. Verify the X-CTF-Signature header before processing:
import hmac, hashlib
def verify_webhook(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
Code example: normalize user uploads to WebP
A common pattern: accept any image format from users, convert to WebP, store in S3. This runs at upload time before the object hits your bucket. Just requests / fetch — no SDK to install.
import requests
import boto3
from uuid import uuid4
CTF_API_KEY = "ctf_sk_your_key_here"
S3_BUCKET = "your-bucket"
def normalize_and_store(upload_path: str, original_filename: str) -> str:
"""Convert uploaded image to WebP, upload to S3, return CDN URL."""
with open(upload_path, "rb") as f:
resp = requests.post(
"https://changethisfile.com/v1/convert",
headers={
"Authorization": f"Bearer {CTF_API_KEY}",
"Idempotency-Key": f"upload-{uuid4()}",
},
files={"file": (original_filename, f)}, # source auto-detected from filename
data={"target": "webp"},
timeout=30,
)
resp.raise_for_status()
key = f"images/{uuid4()}.webp"
s3 = boto3.client("s3")
s3.put_object(
Bucket=S3_BUCKET,
Key=key,
Body=resp.content,
ContentType="image/webp",
)
return f"https://{S3_BUCKET}.s3.amazonaws.com/{key}"
TypeScript version (Node 18+, native fetch):
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const CTF_API_KEY = process.env.CTF_API_KEY!;
const s3 = new S3Client({ region: "us-east-1" });
async function normalizeAndStore(file: Buffer, originalName: string): Promise {
const form = new FormData();
form.append("file", new Blob([file]), originalName); // source auto-detected
form.append("target", "webp");
const r = await fetch("https://changethisfile.com/v1/convert", {
method: "POST",
headers: { Authorization: `Bearer ${CTF_API_KEY}` },
body: form,
});
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
const buf = Buffer.from(await r.arrayBuffer());
const key = `images/${crypto.randomUUID()}.webp`;
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET!,
Key: key,
Body: buf,
ContentType: "image/webp",
}));
return `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${key}`;
}
Pricing for developer scale
| Plan | Conversions/month | Price | Right for |
|---|---|---|---|
| Free | 1,000 | $0 | Development, side projects, low-volume features |
| Hobby | 10,000 | $29/mo | Early-stage apps, internal tools |
| Startup | 50,000 | $99/mo | Production apps with active users |
| Scale | 250,000 | $499/mo | High-throughput pipelines |
| Growth | 1,000,000 | $1,999/mo | Enterprise / platform scale |
All paid plans include priority queue, higher file size limits, and webhook support. The free tier is a real 1,000 conversions — no card, no expiry, no trial gotchas.
FAQ
What file size limits apply?
Free tier: 25MB per file. Paid plans: higher limits — check your plan dashboard. For large video files on the Hobby plan and above, async mode is recommended to avoid timeout issues.
Are conversions processed in parallel or queued?
Synchronous requests are processed immediately up to your plan's concurrency limit. Async jobs go into a queue. Free tier has lower concurrency — for burst workloads, upgrade to Hobby or above.
What happens if a conversion fails?
The API returns a non-2xx status with a JSON error body containing a machine-readable code field. Use this for retry logic. With idempotency keys, retrying a failed request is safe — the same key won't double-bill a successful conversion.
Can I self-host the conversion stack?
The server-side stack is FFmpeg + LibreOffice + Calibre + 7-Zip on Express. Hosting it yourself is possible but involves maintaining security patches, managing concurrency, and handling LibreOffice's single-instance limitation. The API is the alternative for teams that would rather not.
Do you support batch conversions?
Send individual requests — there's no batch endpoint. For high-volume batch jobs, parallelize with a worker pool using idempotency keys to make retries safe. The Startup plan and above have higher concurrency limits that make parallel batches practical.
Where are files stored during conversion?
Uploaded files are stored temporarily during conversion and auto-deleted after. No logs of file content are kept. For client-side routes (images, data, fonts), files never leave the browser.
Related guides
Specific conversion guides for common developer tasks: