TransConvert API
Convert images and documents programmatically with one simple HTTP endpoint.
curl -X POST \ .../api/v1/convert.php \ -H "Authorization: Bearer ..." \ -F "category=image" \ -F "target=PNG" \ -F "file=@photo.jpg" \ -o converted.png
Quickstart
From zero to your first converted file in three steps.
Sign up (or upgrade an existing account) to Basic, Lite, Pro, or Team, then generate a key from your account page — you can come back and view it again any time.
POST your file to the endpoint below as multipart/form-data, with your key in the Authorization header and category + target set.
A 200 response is the converted file's raw bytes — save the response body directly. Anything else is a JSON error explaining what went wrong.
Authentication
Every request needs an API key, sent as a Bearer token in the Authorization header.
Authorization: Bearer tc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Available on the Basic, Lite, Pro, and Team plans. Generate a key from your account →
Test your key
A quick way to confirm a key works before writing any real integration code — this alone won't convert anything (no file attached), but a 400 no_file instead of a 401 confirms the key itself is valid.
curl -X POST -H "Authorization: Bearer tc_live_your_key_here" -F "category=image" https://transconvert.com/api/v1/convert.php
The endpoint
A single endpoint handles every conversion. Send a multipart/form-data POST request with your file and the fields below.
https://transconvert.com/api/v1/convert.php
Parameters
| Field | Description |
|---|---|
Authorization | Required. "Bearer tc_live_...". |
category | Required. "image" or "document" — for compressing rather than converting, see Compress below. |
target | Required. The output format code, e.g. "PNG", "DOCX" — see Supported formats below. |
file | Required. The file to convert (multipart upload). |
pdf_mode | Optional, image category only. "pages" (default, rasterizes each page) or "extract" (pulls out embedded images as-is) — only relevant when the source is a PDF. |
pdf_pages | Optional, image category only. "all" (default) or "first". |
pdf_quality | Optional, image category only. "normal" (default, 150 DPI) or "high" (300 DPI). |
Common conversions
A quick reference for popular pairs — the same endpoint handles all of them, just with a different category/target combination.
| Source → target | category | target |
|---|---|---|
| PNG → JPG | image | JPG |
| JPG → PNG | image | PNG |
| HEIC → JPG | image | JPG |
| WEBP → PNG | image | PNG |
| JPG → PDF | image | PDF |
| PDF → JPG | image | JPG |
| DOCX → PDF | document | PDF |
| PDF → DOCX | document | DOCX |
| PPTX → PDF | document | PDF |
| XLSX → PDF | document | PDF |
Response
On success (200): the converted file's raw bytes, with Content-Type and Content-Disposition headers set for it. On failure: a JSON body shaped {"error": {"code": "...", "message": "..."}} with a matching HTTP status code — see Errors below.
| Header | Value |
|---|---|
Content-Type | The converted file's real MIME type (e.g. image/png, application/pdf). |
Content-Disposition | attachment; filename="..." — a suggested filename, same as any file download. |
Content-Length | Size of the response body in bytes. |
Compress
Shrink a file without changing its format — same format in, same format out. A separate pair of categories from conversion, image-compress and document-compress, each with their own options below.
image-compress
Same format in, same format out (JPG/PNG/WEBP/GIF) — target_percent is how small the result should aim to be relative to the original, not a fixed quality setting.
| Field | Description |
|---|---|
category | Set to "image-compress". |
target_percent | Optional, 1–100 (default 60). Target size as a rough percentage of the original — smaller numbers compress harder. |
file | Required. JPG, PNG, WEBP, or GIF. |
curl -X POST \ https://transconvert.com/api/v1/convert.php \ -H "Authorization: Bearer tc_live_your_key_here" \ -F "category=image-compress" \ -F "target_percent=50" \ -F "file=@photo.jpg" \ -o compressed.jpg
document-compress
PDF in, PDF out, via Ghostscript's own recompression — never returns a file bigger than what was uploaded (falls back to the original if recompression didn't help).
| Field | Description |
|---|---|
category | Set to "document-compress". |
level | Optional: "low", "medium" (default), "high", or "none". Higher compression trades more visual quality, mainly on embedded images/scans. |
grayscale | Optional. "1" to also convert to grayscale; omit for full color. |
file | Required. A PDF that is not open/password-protected (use Unlock PDF on the website first if it is). |
curl -X POST \ https://transconvert.com/api/v1/convert.php \ -H "Authorization: Bearer tc_live_your_key_here" \ -F "category=document-compress" \ -F "level=high" \ -F "file=@report.pdf" \ -o compressed.pdf
| Header | Value |
|---|---|
X-Original-Size | The uploaded file's size in bytes, before compression. |
X-Saved-Percent | Roughly how much smaller the result is than the original, as a whole-number percentage (can be 0). |
Video & audio (async)
Video and audio conversions can run for minutes, too long to hold open a single synchronous request — these use a submit-then-poll flow instead of the endpoint above. Submit a file, get a job_id back right away, then poll for its status until it's done.
Submit a job
Same multipart POST shape as the main endpoint, at a different URL.
https://transconvert.com/api/v1/convert-async.php
| Field | Description |
|---|---|
Authorization | Required. "Bearer tc_live_...". |
category | "video" or "audio". |
target | Required — e.g. "MP4", "MOV", "MP3", "WAV". |
file | Required. The file to convert (multipart upload). |
webhook_url | Optional. An http(s) URL to POST the job result to when it finishes, instead of only polling job-status.php. Must resolve to a public address. |
curl -X POST \ https://transconvert.com/api/v1/convert-async.php \ -H "Authorization: Bearer tc_live_your_key_here" \ -F "category=video" \ -F "target=MP4" \ -F "file=@clip.mov"
Response (202 Accepted)
{ "job_id": "job_242e1555d78d166807aa56502f15d118", "status": "queued" }
Poll for status
Poll this every few seconds with the job_id you got back. "status" is one of queued, processing, completed, or failed.
https://transconvert.com/api/v1/job-status.php?job_id=job_...
{
"job_id": "job_242e1555d78d166807aa56502f15d118",
"status": "completed",
"category": "video",
"target_format": "MP4",
"created_at": "2026-08-26 19:17:47",
"download_url": "/api/v1/job-status.php?job_id=job_...&download=1",
"filename": "clip.mp4"
}
Webhooks (optional)
If you gave a webhook_url when submitting, we'll POST this same JSON body to it once the job finishes — success or failure — retrying a few times if your endpoint doesn't respond. job-status.php still works as a fallback either way.
POST your webhook_url { "job_id": "job_242e1555d78d166807aa56502f15d118", "status": "completed", "category": "video", "target_format": "MP4", "created_at": "2026-08-26 19:17:47", "download_url": "https://transconvert.com/api/v1/job-status.php?job_id=job_...&download=1", "filename": "clip.mp4" }
Download the result
Once status is "completed", the response includes a download_url — the same status URL with &download=1 appended. Requesting it then streams the converted file's raw bytes, same headers as every other endpoint on this page. The result is deleted the moment it's downloaded, or automatically after a short retention window if it's never downloaded.
scheduleJob results are deleted immediately after download, or automatically after a short retention window if never downloaded — download promptly.
Examples
The same request in four languages — pick whichever matches your stack. Each one converts a local photo.jpg to PNG and saves the result.
curl -X POST \ https://transconvert.com/api/v1/convert.php \ -H "Authorization: Bearer tc_live_your_key_here" \ -F "category=image" \ -F "target=PNG" \ -F "file=@photo.jpg" \ -o converted.png
<?php $ch = curl_init('https://transconvert.com/api/v1/convert.php'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ['Authorization: Bearer tc_live_your_key_here'], CURLOPT_POSTFIELDS => [ 'category' => 'image', 'target' => 'PNG', 'file' => new CURLFile('photo.jpg'), ], ]); $response = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($status === 200) { file_put_contents('converted.png', $response); } else { $error = json_decode($response, true); echo $error['error']['message']; }
const form = new FormData(); form.append('category', 'image'); form.append('target', 'PNG'); form.append('file', new Blob([fs.readFileSync('photo.jpg')]), 'photo.jpg'); const res = await fetch('https://transconvert.com/api/v1/convert.php', { method: 'POST', headers: { Authorization: 'Bearer tc_live_your_key_here' }, body: form, }); if (res.ok) { fs.writeFileSync('converted.png', Buffer.from(await res.arrayBuffer())); } else { const { error } = await res.json(); console.error(error.message); }
import requests with open('photo.jpg', 'rb') as f: response = requests.post( 'https://transconvert.com/api/v1/convert.php', headers={'Authorization': 'Bearer tc_live_your_key_here'}, data={'category': 'image', 'target': 'PNG'}, files={'file': f}, ) if response.status_code == 200: with open('converted.png', 'wb') as out: out.write(response.content) else: print(response.json()['error']['message'])
# gem install multipart-post require 'net/http' require 'net/http/post/multipart' url = URI('https://transconvert.com/api/v1/convert.php') File.open('photo.jpg') do |file| req = Net::HTTP::Post::Multipart.new url, 'category' => 'image', 'target' => 'PNG', 'file' => UploadIO.new(file, 'image/jpeg', 'photo.jpg') req['Authorization'] = 'Bearer tc_live_your_key_here' res = Net::HTTP.start(url.host, url.port, use_ssl: true) do |http| http.request(req) end if res.code == '200' File.write('converted.png', res.body) else puts JSON.parse(res.body)['error']['message'] end end
// Gradle: implementation("com.squareup.okhttp3:okhttp:4.+") OkHttpClient client = new OkHttpClient(); RequestBody body = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("category", "image") .addFormDataPart("target", "PNG") .addFormDataPart("file", "photo.jpg", RequestBody.create(new File("photo.jpg"), MediaType.parse("image/jpeg"))) .build(); Request request = new Request.Builder() .url("https://transconvert.com/api/v1/convert.php") .header("Authorization", "Bearer tc_live_your_key_here") .post(body) .build(); try (Response response = client.newCall(request).execute()) { if (response.isSuccessful()) { Files.write(Paths.get("converted.png"), response.body().bytes()); } else { System.err.println(response.body().string()); } }
Errors
Every failure returns a JSON error envelope with a "code" your code can branch on, plus a human-readable "message". Some errors include extra fields (quota_exceeded includes "limit" and "used", for example).
{
"error": {
"code": "quota_exceeded",
"message": "Monthly API allowance of 5000 conversion-minutes reached.",
"limit": 5000,
"used": 5000
}
}
| Status & code | When it happens |
|---|---|
401 missing_key | No Authorization header was sent. |
401 invalid_key | The key doesn't exist, or has been revoked. |
403 account_suspended | The account owning this key is suspended. |
403 plan_required | The account is on the Free plan — API access needs Basic, Lite, Pro, or Team. |
400 invalid_category | "category" wasn't "image" or "document". |
400 missing_target | "target" was empty. |
400 no_file | No file was sent, or the upload failed — the field must be named "file". |
413 file_too_large | The file exceeds your plan's max upload size. |
429 quota_exceeded | The plan's monthly conversion-minutes allowance is used up. Resets at the start of the next calendar month. |
429 concurrency_limit | Too many conversions already running at once for this account (shared with the website) — wait for one to finish and retry. |
400/415/422/500/503 conversion_failed | The file itself couldn't be converted — "message" explains why. The status code varies with the reason: 400/415/422 mean the file or target won't work no matter how many times you retry; 500/503 mean a server-side problem, and 503 specifically is worth a short retry. |
405 method_not_allowed | Wrong HTTP method — this endpoint only accepts POST. |
400 invalid_target | "target" isn't a supported output format for that category. |
404 job_not_found | No job with that id exists for this account (also returned for another account's job_id — its existence is never revealed). |
410 result_gone | The job completed, but its result has since been deleted (results are removed immediately after download, or automatically after a short retention window). |
500 storage_failed | The server couldn't save the upload for background processing. Safe to retry. |
Handling errors & retries
Branch on the JSON "code" field, not the "message" text — wording may change over time, the code won't. concurrency_limit is worth a short retry after a few seconds (it clears as soon as one of your in-flight conversions finishes); quota_exceeded won't resolve itself until next month, so don't retry it in a loop. conversion_failed is the one code where the HTTP status still matters: a 503 is a transient server-side issue worth one short retry, while 400/415/422/500 mean that exact file/target combination won't succeed no matter how many times you resend it. Checking a file's size client-side before uploading avoids wasting a request on a guaranteed file_too_large.
Plans & limits
The API shares its limits with the same plan you already use on the website — nothing separate to configure.
Basic
bolt2000 conversion-minutes / month
upload_fileFiles up to 2 GB
sync_alt50 request(s) at once
speed30 requests/minute
Lite
bolt3000 conversion-minutes / month
upload_fileFiles up to 4 GB
sync_alt100 request(s) at once
speed60 requests/minute
Pro
bolt5000 conversion-minutes / month
upload_fileFiles up to 10 GB
sync_altUnlimited requests at once
speed120 requests/minute
Team
bolt10000 conversion-minutes / month
upload_fileFiles up to 20 GB
sync_altUnlimited requests at once
speed240 requests/minute
tollShared with the team's monthly credit pool, if it uses one
Every response once a per-minute limit applies includes X-RateLimit-Limit and X-RateLimit-Remaining headers, and a 429 response also includes Retry-After (seconds) — use these to slow down before hitting the limit instead of only reacting after a 429.
Supported formats
The exact same conversion engine the website uses — nothing is API-exclusive or website-exclusive.
Accepted as source:
JPG, PNG, GIF, WEBP, BMP, AVIF, PDF, PSD, TIFF, EPS, HEIC
Available as target:
JPG, PNG, GIF, WEBP, BMP, AVIF, PDF, ICO, PSD, TIFF, EPS
Accepted as source:
PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, RTF, ODT, ODP, ODS, HTML
Available as target:
PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, RTF, ODT, ODP, ODS
Source and target (same format in, same format out):
JPG, PNG, WEBP, GIF
Source and target (same format in, same format out):
Source and target (same format in, same format out):
MP4, MOV, AVI, MKV, WEBM
Source and target (same format in, same format out):
MP3, WAV, OGG, AAC, FLAC, M4A, WMA, OPUS, AIFF, AMR, AU, CAF, AC3, DTS, GSM, IRCAM, MP2, TTA, VOC, W64, WV, SPX, RM
infoVideo and audio compression are website-only for now. Video/audio conversion is available through the API via the async submit-then-poll flow below (category "video"/"audio") rather than this single-request endpoint.
Frequently asked questions
Yes, through a separate async endpoint (see "Video & audio (async)" above) — you submit a job and poll for its result instead of one blocking request, since these conversions can take several minutes. Compression for video/audio is still website-only.
API access requires Basic, Lite, Pro, or Team. If the account moves to Free — cancelling, or a subscription lapsing — existing keys stop working immediately. They start working again automatically if the account is back on a paid plan; you don't need to generate a new one.
At the start of each calendar month, not on your billing date.
Not currently — every request counts against your real monthly allowance. Use small files while integrating to conserve it.
Up to your plan's concurrency limit (see Plans & limits above) — shared with any conversions you're also running on the website at the same time, not a separate API-only allowance.
For document-compress, yes — it never hands back a PDF bigger than what was uploaded; if Ghostscript's recompression didn't help, you get the original back unchanged (X-Saved-Percent will read 0). For image-compress, target_percent is a target the encoder aims for, not a hard guarantee — an already-heavily-compressed source may not shrink much further.
Ready to start?
Generate a key and make your first request in under a minute.
Get your API key