0.00 GB / 1.00 GB monthly quota
0.00 GB / 1.00 GB additional quota
0 / 5 daily conversions
/month
Email with pasword reset link sent.
Enter your email address and we'll send you a link to reset your password.
The Convert.Online API lets you convert files programmatically. You describe a job as a small graph of tasks — import a file, convert it, export a download link — and the API runs it asynchronously and hands you back the result.
The base URL for all endpoints is:
https://api.convert.online
Requests and responses are JSON. Every call that touches your account is authenticated with an API key (see Authentication). The fastest way to see a working request is the visual Job Builder, which generates ready-to-run code in eight languages as you build the job.
POST /v1/process/jobs
complete-upload
finished
Authenticate every request with an API key from your dashboard (Dashboard → API Keys). A key is shown once when you create it — store it securely; we only keep a hash.
Send the key as a Bearer token:
Authorization: Bearer <your-api-key>
The header X-API-Key: <your-api-key> is also accepted. Requests without a valid key return 401. If a key is restricted to specific IPs and the request comes from another address, it returns 403.
X-API-Key: <your-api-key>
401
403
req, _ := http.NewRequest("GET", "https://api.convert.online/v1/process/jobs/{JOB_ID}", nil) req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) fmt.Println(string(body))
GET /v1/process/jobs/{JOB_ID} HTTP/1.1 Host: api.convert.online Authorization: Bearer {API_KEY}
HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs/{JOB_ID}")) .header("Authorization", "Bearer {API_KEY}") .GET() .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body());
const res = await fetch("https://api.convert.online/v1/process/jobs/{JOB_ID}", { headers: { "Authorization": "Bearer {API_KEY}" } }); const job = await res.json();
<?php $ch = curl_init("https://api.convert.online/v1/process/jobs/{JOB_ID}"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {API_KEY}"], ]); $job = json_decode(curl_exec($ch), true);
import requests job = requests.get( "https://api.convert.online/v1/process/jobs/{JOB_ID}", headers={"Authorization": "Bearer {API_KEY}"}, ).json()
require "net/http" require "json" require "uri" uri = URI("https://api.convert.online/v1/process/jobs/{JOB_ID}") req = Net::HTTP::Get.new(uri) req["Authorization"] = "Bearer {API_KEY}" res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } job = JSON.parse(res.body)
curl https://api.convert.online/v1/process/jobs/{JOB_ID} \ -H 'Authorization: Bearer {API_KEY}'
Each API key is limited to 60 requests per minute. Exceeding it returns 429 with a retry_after hint.
429
retry_after
Conversions consume your account's storage/transfer quota (measured in GB): the source file size is metered when a job runs, and re-downloading a finished file consumes quota too (the first download of each file is free). When the quota is exhausted, job creation and downloads return 402. Registered free accounts include a monthly allowance; paid plans raise it.
402
Your plan also sets how many conversions may run at the same time, and a single job may not ask for more than that: a plan allowing 5 accepts a job with up to 5 convert tasks, and answers 422 to a job with 6, telling you the allowance and how many you asked for. Send the rest in another job. Paid plans have no such cap — a job may carry up to 100 convert tasks, and they run as server capacity allows.
convert
422
100
Tip: use the Sandbox to develop and test integrations without touching your quota.
A job is a collection of tasks that run as one unit. You submit the whole graph in a single request and track it by its id.
id
Each task has an operation and, usually, an input that wires it to a previous task. The supported operations are:
operation
input
import/upload
import/url
import/base64
output_format
export/url
export/webhook
import/s3
import/sftp
import/googlecloud
export/s3
export/sftp
export/googlecloud
A convert task's input is the name of an import task; an export task's input is an array of convert-task names. A convert's input can also be another convert, so you can chain conversions (A → B → C). See Chained conversions.
Convert a JPG to a PNG end to end. Replace {API_KEY} with your key.
{API_KEY}
You get back a job id and, on the import-1 task, an upload form (URL + headers).
import-1
form
PUT the file bytes to result.form.url using the returned headers.
PUT
result.form.url
Call complete-upload to enqueue the work.
GET the job until status is finished (or failed).
GET
status
failed
The export-1 task's result.url is your download link.
export-1
result.url
package main import ( "bytes" "fmt" "net/http" ) func main() { payload := []byte(`{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }`) req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) }
POST /v1/process/jobs HTTP/1.1 Host: api.convert.online Content-Type: application/json Authorization: Bearer {API_KEY} { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }
import java.net.URI; import java.net.http.*; public class CreateJob { public static void main(String[] args) throws Exception { String body = """ { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }"""; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs")) .header("Content-Type", "application/json") .header("Authorization", "Bearer {API_KEY}") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body()); } }
const res = await fetch("https://api.convert.online/v1/process/jobs", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer {API_KEY}" }, body: JSON.stringify({ tasks: { "import-1": { operation: "import/upload" }, "convert-1": { operation: "convert", input: "import-1", input_format: "jpg", output_format: "png" }, "export-1": { operation: "export/url", input: ["convert-1"] } } }) }); const job = await res.json();
<?php $ch = curl_init("https://api.convert.online/v1/process/jobs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer {API_KEY}", ], CURLOPT_POSTFIELDS => json_encode([ "tasks" => [ "import-1" => ["operation" => "import/upload"], "convert-1" => ["operation" => "convert", "input" => "import-1", "input_format" => "jpg", "output_format" => "png"], "export-1" => ["operation" => "export/url", "input" => ["convert-1"]], ], ]), ]); $job = json_decode(curl_exec($ch), true);
import requests res = requests.post( "https://api.convert.online/v1/process/jobs", headers={"Authorization": "Bearer {API_KEY}"}, json={ "tasks": { "import-1": {"operation": "import/upload"}, "convert-1": {"operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png"}, "export-1": {"operation": "export/url", "input": ["convert-1"]}, } }, ) job = res.json()
require "net/http" require "json" require "uri" uri = URI("https://api.convert.online/v1/process/jobs") http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Authorization"] = "Bearer {API_KEY}" req.body = { tasks: { "import-1" => { operation: "import/upload" }, "convert-1" => { operation: "convert", input: "import-1", input_format: "jpg", output_format: "png" }, "export-1" => { operation: "export/url", input: ["convert-1"] } } }.to_json job = JSON.parse(http.request(req).body)
curl -X POST https://api.convert.online/v1/process/jobs \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {API_KEY}' \ -d '{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }'
// form = the import task's result.form from the create response data, _ := os.ReadFile("photo.jpg") req, _ := http.NewRequest("PUT", form.URL, bytes.NewReader(data)) req.Header.Set("Content-Type", "application/octet-stream") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } resp.Body.Close()
PUT /…signed-put-url… HTTP/1.1 Host: storage.googleapis.com Content-Type: application/octet-stream <binary file bytes>
// formUrl = the import task's result.form.url from the create response HttpRequest req = HttpRequest.newBuilder() .uri(URI.create(formUrl)) .header("Content-Type", "application/octet-stream") .PUT(HttpRequest.BodyPublishers.ofFile(Path.of("photo.jpg"))) .build(); HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
// form = the import task's result.form from the create response await fetch(form.url, { method: form.method, // "PUT" headers: form.headers, // { "Content-Type": "application/octet-stream" } body: fileBlob // a File / Blob / ArrayBuffer });
<?php // $form = the import task's result.form from the create response $ch = curl_init($form["url"]); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => "PUT", CURLOPT_POSTFIELDS => file_get_contents("photo.jpg"), CURLOPT_HTTPHEADER => ["Content-Type: application/octet-stream"], CURLOPT_RETURNTRANSFER => true, ]); curl_exec($ch);
import requests # form = the import task's result.form from the create response with open("photo.jpg", "rb") as f: requests.put(form["url"], data=f, headers=form["headers"])
require "net/http" require "uri" # form = the import task's result.form from the create response uri = URI(form["url"]) req = Net::HTTP::Put.new(uri) req["Content-Type"] = "application/octet-stream" req.body = File.binread("photo.jpg") Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
# Use the form.url + form.headers from the import task's result curl -X PUT "https://storage.googleapis.com/…signed-put-url…" \ -H 'Content-Type: application/octet-stream' \ --data-binary @photo.jpg
req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs/{JOB_ID}/complete-upload", nil) req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } resp.Body.Close()
POST /v1/process/jobs/{JOB_ID}/complete-upload HTTP/1.1 Host: api.convert.online Authorization: Bearer {API_KEY}
HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs/{JOB_ID}/complete-upload")) .header("Authorization", "Bearer {API_KEY}") .POST(HttpRequest.BodyPublishers.noBody()) .build(); HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
await fetch("https://api.convert.online/v1/process/jobs/{JOB_ID}/complete-upload", { method: "POST", headers: { "Authorization": "Bearer {API_KEY}" } });
<?php $ch = curl_init("https://api.convert.online/v1/process/jobs/{JOB_ID}/complete-upload"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {API_KEY}"], ]); curl_exec($ch);
import requests requests.post( "https://api.convert.online/v1/process/jobs/{JOB_ID}/complete-upload", headers={"Authorization": "Bearer {API_KEY}"}, )
require "net/http" require "uri" uri = URI("https://api.convert.online/v1/process/jobs/{JOB_ID}/complete-upload") req = Net::HTTP::Post.new(uri) req["Authorization"] = "Bearer {API_KEY}" Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
curl -X POST https://api.convert.online/v1/process/jobs/{JOB_ID}/complete-upload \ -H 'Authorization: Bearer {API_KEY}'
HTTP/1.1 200 OK { "id": "b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6", "status": "finished", "createdAt": "2026-07-20T10:00:00+00:00", "endedAt": "2026-07-20T10:00:07+00:00", "tasks": [ { "name": "convert-1", "operation": "convert", "status": "finished" }, { "name": "export-1", "operation": "export/url", "status": "finished", "result": { "url": "https://api.convert.online/v1/download/eyJ…token…", "filename": "photo.png" } } ], "links": { "self": "/v1/process/jobs/b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6" } }
req, _ := http.NewRequest("GET", "https://api.convert.online/v1/download/eyJ…token…", nil) req.Header.Set("Authorization", "Bearer {API_KEY}") resp, _ := http.DefaultClient.Do(req) defer resp.Body.Close() out, _ := os.Create("photo.png") defer out.Close() io.Copy(out, resp.Body)
GET /v1/download/eyJ…token… HTTP/1.1 Host: api.convert.online Authorization: Bearer {API_KEY}
HttpClient client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) .build(); HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/download/eyJ…token…")) .header("Authorization", "Bearer {API_KEY}") .build(); client.send(req, HttpResponse.BodyHandlers.ofFile(Path.of("photo.png")));
const res = await fetch("https://api.convert.online/v1/download/eyJ…token…", { headers: { "Authorization": "Bearer {API_KEY}" } }); const blob = await res.blob(); // save/stream as needed
<?php $ch = curl_init("https://api.convert.online/v1/download/eyJ…token…"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true, CURLOPT_HTTPHEADER => ["Authorization: Bearer {API_KEY}"], ]); file_put_contents("photo.png", curl_exec($ch));
import requests # The export result.url is a ready-to-use link (redirects to storage). r = requests.get("https://api.convert.online/v1/download/eyJ…token…", headers={"Authorization": "Bearer {API_KEY}"}, allow_redirects=True) with open("photo.png", "wb") as f: f.write(r.content)
require "open-uri" URI.open("https://api.convert.online/v1/download/eyJ…token…", "Authorization" => "Bearer {API_KEY}") do |src| File.binwrite("photo.png", src.read) end
# The export result.url is already a full, ready-to-use link: curl -L "https://api.convert.online/v1/download/eyJ…token…" -o photo.png
Submit the job graph as a tasks object. For every import/upload task, the response includes a signed upload form. The job starts in waiting_upload.
tasks
waiting_upload
tag
sandbox
Convert task fields: operation = convert, input (a task name), input_format, output_format, and an optional options object with format-specific settings.
input_format
options
Every import must feed a convert. An import/upload, import/url or cloud-storage task that no convert task takes as input is rejected (422) — otherwise we would provision or download a file nothing would ever use. An export wired straight to an import is rejected for the same reason: only a convert produces the output an export delivers.
export
An export task is optional. Without one, the convert task's own result still carries a download_url to the finished file — export tasks are for delivering the result somewhere specific (a URL you fetch, a webhook, your own S3/SFTP/GCS).
result
download_url
Note: you may convert up to a fixed number of files per job. There is also a shortcut for a pre-created import: POST /v1/imports to get an upload form, then POST /v1/process/jobs with { "import_id": "…", "output_format": "png" }.
POST /v1/imports
{ "import_id": "…", "output_format": "png" }
HTTP/1.1 201 Created { "id": "b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6", "tag": "", "status": "waiting_upload", "createdAt": "2026-07-20T10:00:00+00:00", "tasks": [ { "id": "import-1", "name": "import-1", "operation": "import/upload", "status": "waiting_upload", "result": { "form": { "url": "https://storage.googleapis.com/…signed-put-url…", "method": "PUT", "parameters": {}, "headers": { "Content-Type": "application/octet-stream" }, "expiresAt": "2026-07-21T10:00:00+00:00" } } }, { "id": "convert-1", "name": "convert-1", "operation": "convert", "status": "waiting_input", "dependsOn": ["import-1"] }, { "id": "export-1", "name": "export-1", "operation": "export/url", "status": "waiting_input", "dependsOn": ["convert-1"] } ], "links": { "self": "/v1/process/jobs/b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6" } }
The create response returns an upload form on each import/upload task: result.form.url, result.form.method (PUT) and result.form.headers. Send the raw file bytes to that URL with those headers. The URL is a short-lived signed link; upload before it expires.
result.form.method
result.form.headers
The bytes go straight from you to storage — they never pass through our servers — so an upload has no fixed size limit: the only ceiling is the quota you have left. (A file fetched by us, with import/url or from cloud storage, does have one, because we download it first.)
Send the file as a stream rather than reading it into memory. The Content-Type must be exactly the one in result.form.headers — it is part of what the URL is signed with, and anything else is rejected as a bad signature.
Content-Type
Instead of uploading, you can let us fetch the source from a public URL. Use an import/url task with a url field, then point a convert at it — there is no upload step for that file.
url
Creating the job returns immediately — we do not download the source on the request. The fetch happens when the conversion runs, so a slow source never holds your request open. The URL must be publicly reachable over http or https (private, loopback and link-local addresses are rejected, and redirects are followed only to public addresses); the source may be up to 5 GB. The fetched bytes count toward your quota just like an uploaded file.
http
https
You still call complete-upload to start the conversion — that is what hands the job to a worker, which fetches the source and then converts. A single job can freely mix import/url and import/upload tasks.
For small files you can skip both upload and fetch and put the bytes in the request with an import/base64 task: set file to the base64-encoded content (a data: URI prefix is accepted and stripped). It is size-limited after decoding and counts toward your quota. Prefer import/upload or import/url for anything but small inputs.
file
data:
package main import ( "bytes" "fmt" "net/http" ) func main() { payload := []byte(`{ "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }`) req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) }
POST /v1/process/jobs HTTP/1.1 Host: api.convert.online Content-Type: application/json Authorization: Bearer {API_KEY} { "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }
import java.net.URI; import java.net.http.*; public class CreateJob { public static void main(String[] args) throws Exception { String body = """ { "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }"""; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs")) .header("Content-Type", "application/json") .header("Authorization", "Bearer {API_KEY}") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body()); } }
const res = await fetch("https://api.convert.online/v1/process/jobs", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer {API_KEY}" }, body: `{ "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }` }); const job = await res.json();
<?php $body = <<<'JSON' { "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } } JSON; $ch = curl_init("https://api.convert.online/v1/process/jobs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer {API_KEY}", ], CURLOPT_POSTFIELDS => $body, ]); $job = json_decode(curl_exec($ch), true);
import requests body = '''{ "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }''' res = requests.post( "https://api.convert.online/v1/process/jobs", headers={"Content-Type": "application/json", "Authorization": "Bearer {API_KEY}"}, data=body, ) job = res.json()
require "net/http" require "json" require "uri" body = <<~JSON { "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } } JSON uri = URI("https://api.convert.online/v1/process/jobs") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Authorization"] = "Bearer {API_KEY}" req.body = body res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } job = JSON.parse(res.body)
curl -X POST https://api.convert.online/v1/process/jobs \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {API_KEY}' \ -d '{ "tasks": { "import-1": { "operation": "import/url", "url": "https://example.com/photo.jpg" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }'
package main import ( "bytes" "fmt" "net/http" ) func main() { payload := []byte(`{ "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }`) req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) }
POST /v1/process/jobs HTTP/1.1 Host: api.convert.online Content-Type: application/json Authorization: Bearer {API_KEY} { "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }
import java.net.URI; import java.net.http.*; public class CreateJob { public static void main(String[] args) throws Exception { String body = """ { "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }"""; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs")) .header("Content-Type", "application/json") .header("Authorization", "Bearer {API_KEY}") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body()); } }
const res = await fetch("https://api.convert.online/v1/process/jobs", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer {API_KEY}" }, body: `{ "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }` }); const job = await res.json();
<?php $body = <<<'JSON' { "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } } JSON; $ch = curl_init("https://api.convert.online/v1/process/jobs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer {API_KEY}", ], CURLOPT_POSTFIELDS => $body, ]); $job = json_decode(curl_exec($ch), true);
import requests body = '''{ "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }''' res = requests.post( "https://api.convert.online/v1/process/jobs", headers={"Content-Type": "application/json", "Authorization": "Bearer {API_KEY}"}, data=body, ) job = res.json()
require "net/http" require "json" require "uri" body = <<~JSON { "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } } JSON uri = URI("https://api.convert.online/v1/process/jobs") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Authorization"] = "Bearer {API_KEY}" req.body = body res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } job = JSON.parse(res.body)
curl -X POST https://api.convert.online/v1/process/jobs \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {API_KEY}' \ -d '{ "tasks": { "import-1": { "operation": "import/base64", "file": "iVBORw0KGgoAAAANSUhEUgAA…" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }'
Import directly from — and export directly to — your own Amazon S3 (or S3-compatible), SFTP server, or Google Cloud Storage. You pass the credentials on the task; they are used only to run that job and are never returned in any API response. The source is fetched when the conversion runs (not while your create request is open); exports run after the conversion finishes, so a failed export shows on that export task's result.
bucket
region
us-east-1
key
/
access_key_id
secret_access_key
session_token
endpoint
host
port
22
username
password
private_key
path
filename
client_email
project_id
Imports are size-limited like import/url. Only public hosts are allowed for SFTP and custom S3 endpoints.
package main import ( "bytes" "fmt" "net/http" ) func main() { payload := []byte(`{ "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } }`) req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) }
POST /v1/process/jobs HTTP/1.1 Host: api.convert.online Content-Type: application/json Authorization: Bearer {API_KEY} { "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } }
import java.net.URI; import java.net.http.*; public class CreateJob { public static void main(String[] args) throws Exception { String body = """ { "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } }"""; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs")) .header("Content-Type", "application/json") .header("Authorization", "Bearer {API_KEY}") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body()); } }
const res = await fetch("https://api.convert.online/v1/process/jobs", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer {API_KEY}" }, body: `{ "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } }` }); const job = await res.json();
<?php $body = <<<'JSON' { "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } } JSON; $ch = curl_init("https://api.convert.online/v1/process/jobs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer {API_KEY}", ], CURLOPT_POSTFIELDS => $body, ]); $job = json_decode(curl_exec($ch), true);
import requests body = '''{ "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } }''' res = requests.post( "https://api.convert.online/v1/process/jobs", headers={"Content-Type": "application/json", "Authorization": "Bearer {API_KEY}"}, data=body, ) job = res.json()
require "net/http" require "json" require "uri" body = <<~JSON { "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } } JSON uri = URI("https://api.convert.online/v1/process/jobs") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Authorization"] = "Bearer {API_KEY}" req.body = body res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } job = JSON.parse(res.body)
curl -X POST https://api.convert.online/v1/process/jobs \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {API_KEY}' \ -d '{ "tasks": { "in": { "operation": "import/s3", "bucket": "my-input-bucket", "region": "us-east-1", "key": "photos/a.png", "access_key_id": "AKIA…", "secret_access_key": "…" }, "conv": { "operation": "convert", "input": "in", "input_format": "png", "output_format": "jpg" }, "out": { "operation": "export/sftp", "input": ["conv"], "host": "sftp.example.com", "username": "me", "password": "…", "path": "/uploads/" } } }'
POST /v1/process/jobs/{id}/complete-upload
Call this once all uploads are done. It enqueues the convert tasks and moves the job to processing. Root converts (fed by an uploaded import) start immediately; converts fed by another convert run after their upstream finishes.
processing
GET /v1/process/jobs/{id}
Returns the job with its tasks and their results. Poll this until the job reaches a terminal state. The export task's result.url is the download link once finished.
GET /v1/process/jobs/{id}/events
Opens a Server-Sent Events stream that pushes the job's status as it changes, ending when the job is finished/failed. Useful to avoid polling.
The stream stays open for the whole conversion, so it must be served without proxy buffering. If your client or network can't hold a long connection, polling (Get a job) is the simpler and equally reliable option.
package main import ( "bufio" "fmt" "net/http" ) func main() { req, _ := http.NewRequest("GET", "https://api.convert.online/v1/process/jobs/{JOB_ID}/events", nil) req.Header.Set("Authorization", "Bearer {API_KEY}") req.Header.Set("Accept", "text/event-stream") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() scanner := bufio.NewScanner(resp.Body) for scanner.Scan() { fmt.Println(scanner.Text()) } }
GET /v1/process/jobs/{JOB_ID}/events HTTP/1.1 Host: api.convert.online Authorization: Bearer {API_KEY} Accept: text/event-stream
import java.net.URI; import java.net.http.*; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs/{JOB_ID}/events")) .header("Authorization", "Bearer {API_KEY}") .header("Accept", "text/event-stream") .build(); HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofLines()) .body().forEach(System.out::println);
const res = await fetch("https://api.convert.online/v1/process/jobs/{JOB_ID}/events", { headers: { "Authorization": "Bearer {API_KEY}", "Accept": "text/event-stream" } }); const reader = res.body.getReader(); const decoder = new TextDecoder(); while (true) { const { value, done } = await reader.read(); if (done) break; console.log(decoder.decode(value)); }
<?php $ch = curl_init("https://api.convert.online/v1/process/jobs/{JOB_ID}/events"); curl_setopt_array($ch, [ CURLOPT_HTTPHEADER => ["Authorization: Bearer {API_KEY}", "Accept: text/event-stream"], CURLOPT_WRITEFUNCTION => function ($ch, $chunk) { echo $chunk; // each event as it arrives return strlen($chunk); }, ]); curl_exec($ch);
import requests with requests.get( "https://api.convert.online/v1/process/jobs/{JOB_ID}/events", headers={"Authorization": "Bearer {API_KEY}", "Accept": "text/event-stream"}, stream=True, ) as r: for line in r.iter_lines(): if line: print(line.decode())
require "net/http" require "uri" uri = URI("https://api.convert.online/v1/process/jobs/{JOB_ID}/events") Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http| req = Net::HTTP::Get.new(uri) req["Authorization"] = "Bearer {API_KEY}" req["Accept"] = "text/event-stream" http.request(req) { |res| res.read_body { |chunk| print chunk } } end
curl -N https://api.convert.online/v1/process/jobs/{JOB_ID}/events \ -H 'Authorization: Bearer {API_KEY}'
GET /v1/download/{token}
A finished export/url task returns a ready-to-use download link in result.url — that link is this endpoint. Fetching it meters the download against your quota (the first download of each file is free) and then 302-redirects to a short-lived storage URL, so follow redirects. Re-downloads after the free one consume quota; once exhausted it returns 402.
302
One job can carry several files: add multiple import/upload + convert + export/url task sets. Each import gets its own upload form; upload them all, then call complete-upload once. Every convert runs, and the job is finished only when all converts succeed (it is failed if any fails, with per-task results preserved). There is a limit on the number of convert tasks per job.
A convert task's input can be another convert, so a file flows through several conversions in one job (for example PNG → PDF → JPG). Set the downstream convert's input to the upstream convert's name and its input_format to the upstream's output_format.
The graph must be acyclic and trace back to an import. Each step is a real conversion, so each step meters quota.
package main import ( "bytes" "fmt" "net/http" ) func main() { payload := []byte(`{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } }`) req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) }
POST /v1/process/jobs HTTP/1.1 Host: api.convert.online Content-Type: application/json Authorization: Bearer {API_KEY} { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } }
import java.net.URI; import java.net.http.*; public class CreateJob { public static void main(String[] args) throws Exception { String body = """ { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } }"""; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs")) .header("Content-Type", "application/json") .header("Authorization", "Bearer {API_KEY}") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body()); } }
const res = await fetch("https://api.convert.online/v1/process/jobs", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer {API_KEY}" }, body: `{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } }` }); const job = await res.json();
<?php $body = <<<'JSON' { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } } JSON; $ch = curl_init("https://api.convert.online/v1/process/jobs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer {API_KEY}", ], CURLOPT_POSTFIELDS => $body, ]); $job = json_decode(curl_exec($ch), true);
import requests body = '''{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } }''' res = requests.post( "https://api.convert.online/v1/process/jobs", headers={"Content-Type": "application/json", "Authorization": "Bearer {API_KEY}"}, data=body, ) job = res.json()
require "net/http" require "json" require "uri" body = <<~JSON { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } } JSON uri = URI("https://api.convert.online/v1/process/jobs") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Authorization"] = "Bearer {API_KEY}" req.body = body res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } job = JSON.parse(res.body)
curl -X POST https://api.convert.online/v1/process/jobs \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {API_KEY}' \ -d '{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "png", "output_format": "pdf" }, "convert-2": { "operation": "convert", "input": "convert-1", "input_format": "pdf", "output_format": "jpg" }, "export-1": { "operation": "export/url", "input": ["convert-2"] } } }'
Get notified when a job finishes instead of polling. Add an export/webhook task with a url (or set a job-level webhook_url). When the job reaches a terminal state, we POST a JSON event to that URL.
webhook_url
POST
Each delivery carries these headers:
X-Convert-Event
job.finished
job.failed
X-Convert-Signature
sha256=<HMAC-SHA256 of the raw body>
X-Convert-Delivery
X-Convert-Attempt
Delivery is retried with exponential backoff (up to 5 attempts: immediately, then +30s, +2m, +10m, +30m). Respond 2xx to acknowledge. Only public http(s) targets are allowed.
2xx
http(s)
Every delivery is signed so you can confirm it genuinely came from us. Take your webhook signing secret from Dashboard → API Keys, compute HMAC-SHA256(raw_request_body) with it, and compare — using a constant-time comparison — against the hex value in the X-Convert-Signature header (the part after sha256=). Sign the raw body bytes, before any JSON parsing. Reject the request if it doesn't match. You can rotate the secret from the dashboard at any time.
HMAC-SHA256(raw_request_body)
sha256=
package main import ( "bytes" "fmt" "net/http" ) func main() { payload := []byte(`{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } }`) req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer {API_KEY}") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) }
POST /v1/process/jobs HTTP/1.1 Host: api.convert.online Content-Type: application/json Authorization: Bearer {API_KEY} { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } }
import java.net.URI; import java.net.http.*; public class CreateJob { public static void main(String[] args) throws Exception { String body = """ { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } }"""; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs")) .header("Content-Type", "application/json") .header("Authorization", "Bearer {API_KEY}") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body()); } }
const res = await fetch("https://api.convert.online/v1/process/jobs", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": "Bearer {API_KEY}" }, body: `{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } }` }); const job = await res.json();
<?php $body = <<<'JSON' { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } } JSON; $ch = curl_init("https://api.convert.online/v1/process/jobs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "Authorization: Bearer {API_KEY}", ], CURLOPT_POSTFIELDS => $body, ]); $job = json_decode(curl_exec($ch), true);
import requests body = '''{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } }''' res = requests.post( "https://api.convert.online/v1/process/jobs", headers={"Content-Type": "application/json", "Authorization": "Bearer {API_KEY}"}, data=body, ) job = res.json()
require "net/http" require "json" require "uri" body = <<~JSON { "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } } JSON uri = URI("https://api.convert.online/v1/process/jobs") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req["Authorization"] = "Bearer {API_KEY}" req.body = body res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } job = JSON.parse(res.body)
curl -X POST https://api.convert.online/v1/process/jobs \ -H 'Content-Type: application/json' \ -H 'Authorization: Bearer {API_KEY}' \ -d '{ "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] }, "notify": { "operation": "export/webhook", "url": "https://your-app.example.com/hooks/convert" } } }'
POST /hooks/convert (from Convert.Online) X-Convert-Event: job.finished X-Convert-Signature: sha256=9f86d08… X-Convert-Delivery: 8f3b2c1a9d0e4f5a X-Convert-Attempt: 1 { "event": "job.finished", "id": "b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6", "status": "finished", "tasks": { "export-1": { "operation": "export/url", "status": "finished", "result": { "url": "https://api.convert.online/v1/download/eyJ…", "filename": "photo.png" } } } }
import ( "crypto/hmac" "crypto/sha256" "encoding/hex" ) // secret = your webhook signing secret from the dashboard func verify(rawBody []byte, signatureHeader, secret string) bool { mac := hmac.New(sha256.New, []byte(secret)) mac.Write(rawBody) expected := "sha256=" + hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(expected), []byte(signatureHeader)) }
import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.security.MessageDigest; // secret = your webhook signing secret from the dashboard boolean verify(byte[] rawBody, String signatureHeader, String secret) throws Exception { Mac mac = Mac.getInstance("HmacSHA256"); mac.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256")); byte[] h = mac.doFinal(rawBody); StringBuilder sb = new StringBuilder("sha256="); for (byte b : h) sb.append(String.format("%02x", b)); return MessageDigest.isEqual(sb.toString().getBytes(), signatureHeader.getBytes()); }
const crypto = require("crypto"); // secret = your webhook signing secret from the dashboard function verify(rawBody, signatureHeader, secret) { const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex"); const a = Buffer.from(signatureHeader); const b = Buffer.from(expected); return a.length === b.length && crypto.timingSafeEqual(a, b); } // Express: use express.raw({type: "*/*"}) so req.body is the exact bytes app.post("/hooks/convert", (req, res) => { if (!verify(req.body, req.get("X-Convert-Signature"), process.env.WEBHOOK_SECRET)) { return res.status(401).end(); } const event = JSON.parse(req.body.toString()); // … handle event.status … res.sendStatus(200); });
<?php // $secret = your webhook signing secret from the dashboard $raw = file_get_contents("php://input"); $sig = $_SERVER["HTTP_X_CONVERT_SIGNATURE"] ?? ""; $expected = "sha256=" . hash_hmac("sha256", $raw, $secret); if (!hash_equals($expected, $sig)) { http_response_code(401); exit; } $event = json_decode($raw, true); // … handle $event["status"] … http_response_code(200);
import hmac, hashlib def verify(raw_body: bytes, signature_header: str, secret: str) -> bool: expected = "sha256=" + hmac.new( secret.encode(), raw_body, hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature_header or "") # Flask @app.post("/hooks/convert") def convert_hook(): if not verify(request.get_data(), request.headers.get("X-Convert-Signature"), WEBHOOK_SECRET): abort(401) event = request.get_json() # … handle event["status"] … return "", 200
require "openssl" require "rack/utils" # secret = your webhook signing secret from the dashboard def verify(raw_body, signature_header, secret) expected = "sha256=" + OpenSSL::HMAC.hexdigest("SHA256", secret, raw_body) Rack::Utils.secure_compare(expected, signature_header.to_s) end
Add "sandbox": true to the create body to simulate a job: it is validated and a realistic finished response is returned with placeholder download URLs — but no conversion runs, no file is uploaded, no API key is required, and no quota is used. Use it to develop and test your integration and to see the exact response shape.
"sandbox": true
package main import ( "bytes" "fmt" "net/http" ) func main() { payload := []byte(`{ "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }`) req, _ := http.NewRequest("POST", "https://api.convert.online/v1/process/jobs", bytes.NewBuffer(payload)) req.Header.Set("Content-Type", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { panic(err) } defer resp.Body.Close() fmt.Println("Status:", resp.Status) }
POST /v1/process/jobs HTTP/1.1 Host: api.convert.online Content-Type: application/json { "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }
import java.net.URI; import java.net.http.*; public class CreateJob { public static void main(String[] args) throws Exception { String body = """ { "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }"""; HttpRequest req = HttpRequest.newBuilder() .uri(URI.create("https://api.convert.online/v1/process/jobs")) .header("Content-Type", "application/json") .POST(HttpRequest.BodyPublishers.ofString(body)) .build(); HttpResponse<String> res = HttpClient.newHttpClient() .send(req, HttpResponse.BodyHandlers.ofString()); System.out.println(res.body()); } }
const res = await fetch("https://api.convert.online/v1/process/jobs", { method: "POST", headers: { "Content-Type": "application/json" }, body: `{ "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }` }); const job = await res.json();
<?php $body = <<<'JSON' { "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } } JSON; $ch = curl_init("https://api.convert.online/v1/process/jobs"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", ], CURLOPT_POSTFIELDS => $body, ]); $job = json_decode(curl_exec($ch), true);
import requests body = '''{ "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }''' res = requests.post( "https://api.convert.online/v1/process/jobs", headers={"Content-Type": "application/json"}, data=body, ) job = res.json()
require "net/http" require "json" require "uri" body = <<~JSON { "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } } JSON uri = URI("https://api.convert.online/v1/process/jobs") req = Net::HTTP::Post.new(uri) req["Content-Type"] = "application/json" req.body = body res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) } job = JSON.parse(res.body)
curl -X POST https://api.convert.online/v1/process/jobs \ -H 'Content-Type: application/json' \ -d '{ "sandbox": true, "tasks": { "import-1": { "operation": "import/upload" }, "convert-1": { "operation": "convert", "input": "import-1", "input_format": "jpg", "output_format": "png" }, "export-1": { "operation": "export/url", "input": ["convert-1"] } } }'
HTTP/1.1 200 OK { "id": "sandbox-4f8a2c1b9d0e3a6c", "status": "finished", "sandbox": true, "tasks": [ { "name": "export-1", "operation": "export/url", "status": "finished", "result": { "url": "https://api.convert.online/v1/download/SANDBOX-SAMPLE", "filename": "sample-output" } } ], "note": "Sandbox simulation — no conversion ran and no quota was used." }
Errors are returned as JSON with an HTTP status code:
{ "status": "error", "message": "…" }
404
409
503
The visual Job Builder lets you assemble a job by wiring import/convert/export nodes, set per-format options, and see the request payload and ready-to-run code in eight languages update live. You can also run the job (Live) or simulate it (Sandbox) straight from the page.
The Model Context Protocol server at https://mcp.convert.online exposes conversions to AI assistants (ChatGPT, Claude and other MCP clients) over Streamable HTTP. Tools: list_formats, convert (from a public URL) and get_job. Authenticate with your API key.
https://mcp.convert.online
list_formats
get_job