Conversions
Synchronous conversion
POST /v1/convert — submit a file, get the converted file back inline.
POST /v1/convert is the simplest way to convert a file. Submit a multipart/form-data upload; the server processes the conversion and returns the converted bytes in the response body.
When to use it #
- Files under 100 MB
- You can wait synchronously for the result (typical: 50ms–60s depending on route)
- You don't need a webhook callback
For files larger than 100 MB, or for predictable async behaviour, use POST /v1/jobs.
Request #
POST /v1/convert HTTP/1.1
Host: changethisfile.com
Authorization: Bearer ctf_sk_...your_key_here...
Content-Type: multipart/form-data; boundary=…
--…
Content-Disposition: form-data; name="file"; filename="photo.png"
Content-Type: application/octet-stream
<binary>
--…
Content-Disposition: form-data; name="target"
jpg
--…
Content-Disposition: form-data; name="quality"
85
--…--| Field | Type | Required | Description |
|---|---|---|---|
file | binary | yes | The input file. |
target | string | yes | Target format extension (case-insensitive). The only field most callers need. |
source | string | no | Source format extension. Auto-detected from the filename and (if needed) magic bytes. Pass it explicitly only for renamed uploads. |
quality | integer 1–100 | no | Optional quality preset for lossy image / video / audio routes. |
Optional headers #
| Header | Description |
|---|---|
Idempotency-Key | Stripe-style idempotency key (≤128 chars). See Idempotency. |
Accept | Default is application/octet-stream (binary). Set Accept: application/json to receive a JSON envelope with a signed download URL instead. |
Response #
200 OK — converted file inline #
HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Disposition: attachment; filename="photo.jpg"
X-Conversion-Time: 412ms
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1735689600
<binary JPEG>| Header | Description |
|---|---|
Content-Disposition | Suggested filename for the converted file. |
X-Conversion-Time | End-to-end conversion duration. |
X-RateLimit-Limit / X-RateLimit-Remaining / X-RateLimit-Reset | Per-minute rate-limit window state. |
202 Accepted — too large, queued #
If the file exceeds the inline-processing threshold (~100 MB), the server queues a job and returns:
{
"job_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"status": "queued",
"status_url": "https://changethisfile.com/v1/jobs/f47ac10b-58cc-4372-a567-0e02b2c3d479"
}Poll GET /v1/jobs/{job_id} until status is completed.
Quality presets #
| Route family | quality interpretation |
|---|---|
| Lossy image (JPG, WebP, AVIF) | Encoder quality 1-100 (passed straight to libvips / sharp). |
| Lossy video (MP4, WebM) | Mapped to FFmpeg -crf curve (1=highest CRF/lowest quality, 100=lowest CRF). |
| Lossy audio (MP3, AAC, OGG) | Mapped to bitrate presets (e.g. 32 kb/s @ q=10, 320 kb/s @ q=100). |
| Document, ebook, archive routes | Ignored. |
Code samples #
No SDK to install — just an HTTP POST.
curl -X POST https://changethisfile.com/v1/convert \
-H "Authorization: Bearer ctf_sk_..." \
-F "file=@photo.png" \
-F "target=jpg" \
-F "quality=85" \
--output photo.jpg
import requests
with open("photo.png", "rb") as f:
r = requests.post(
"https://changethisfile.com/v1/convert",
headers={"Authorization": "Bearer ctf_sk_..."},
files={"file": f},
data={"target": "jpg", "quality": 85},
)
r.raise_for_status()
open("photo.jpg", "wb").write(r.content)
print(r.headers.get("X-Conversion-Time"), len(r.content), "bytes")
import { readFileSync, writeFileSync } from 'node:fs';
const form = new FormData();
form.append('file', new Blob([readFileSync('photo.png')]), 'photo.png');
form.append('target', 'jpg');
form.append('quality', '85');
const r = await fetch('https://changethisfile.com/v1/convert', {
method: 'POST',
headers: { Authorization: 'Bearer ctf_sk_...' },
body: form,
});
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
writeFileSync('photo.jpg', Buffer.from(await r.arrayBuffer()));
Failure modes #
| HTTP | error.code | Cause |
|---|---|---|
400 | bad_request | Missing field, invalid source/target, or unparseable multipart body. |
401 | invalid_api_key | Missing or revoked key. |
413 | file_too_large | File exceeds the active plan's max size. |
422 | unsupported_route | This source→target combo isn't in our routing table. See Formats. |
429 | rate_limited / quota_exceeded | Per-minute throttle or monthly quota hit. |
502 | service_unavailable | Conversion engine temporarily unavailable. Safe to retry. |