Step 1: Send a file
POST your file as multipart/form-data to /api/v1/convert with a to_format parameter telling us the target format.
curl -X POST https://cleverutils.com/api/v1/convert \
-F "[email protected]" \
-F "to_format=jpg"
import requests
resp = requests.post(
"https://cleverutils.com/api/v1/convert",
files={"file": open("photo.heic", "rb")},
data={"to_format": "jpg"},
)
job = resp.json()["data"]
print(job["job_id"], job["output"]["url"])
import fs from "node:fs";
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("photo.heic")]), "photo.heic");
form.append("to_format", "jpg");
const resp = await fetch("https://cleverutils.com/api/v1/convert", {
method: "POST",
body: form,
});
const { data: job } = await resp.json();
console.log(job.job_id, job.output.url);
<?php
$ch = curl_init("https://cleverutils.com/api/v1/convert");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
"file" => new CURLFile("photo.heic"),
"to_format" => "jpg",
],
]);
$resp = json_decode(curl_exec($ch), true);
$job = $resp["data"];
echo $job["job_id"], "\n", $job["output"]["url"];
package main
import (
"bytes"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
body := &bytes.Buffer{}
w := multipart.NewWriter(body)
f, _ := os.Open("photo.heic")
fw, _ := w.CreateFormFile("file", "photo.heic")
io.Copy(fw, f)
w.WriteField("to_format", "jpg")
w.Close()
req, _ := http.NewRequest("POST", "https://cleverutils.com/api/v1/convert", body)
req.Header.Set("Content-Type", w.FormDataContentType())
resp, _ := http.DefaultClient.Do(req)
var out struct {
Data struct{ JobID string `json:"job_id"` } `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&out)
println(out.Data.JobID)
}
require 'net/http'
require 'json'
uri = URI('https://cleverutils.com/api/v1/convert')
req = Net::HTTP::Post.new(uri)
req.set_form(
[
['file', File.open('photo.heic')],
['to_format', 'jpg']
],
'multipart/form-data'
)
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
job = JSON.parse(res.body)['data']
puts job['job_id'], job['output']['url']
// Java 11+ HttpClient with multipart (using a small helper)
import java.net.URI;
import java.net.http.*;
import java.nio.file.*;
var boundary = "----CleverUtils" + System.currentTimeMillis();
var CRLF = "\r\n";
var file = Path.of("photo.heic");
var fileBytes = Files.readAllBytes(file);
var body = ("--" + boundary + CRLF
+ "Content-Disposition: form-data; name=\"to_format\"" + CRLF + CRLF + "jpg" + CRLF
+ "--" + boundary + CRLF
+ "Content-Disposition: form-data; name=\"file\"; filename=\"photo.heic\"" + CRLF
+ "Content-Type: application/octet-stream" + CRLF + CRLF).getBytes();
var tail = (CRLF + "--" + boundary + "--" + CRLF).getBytes();
var full = new byte[body.length + fileBytes.length + tail.length];
System.arraycopy(body, 0, full, 0, body.length);
System.arraycopy(fileBytes, 0, full, body.length, fileBytes.length);
System.arraycopy(tail, 0, full, body.length + fileBytes.length, tail.length);
var req = HttpRequest.newBuilder(URI.create("https://cleverutils.com/api/v1/convert"))
.header("Content-Type", "multipart/form-data; boundary=" + boundary)
.POST(HttpRequest.BodyPublishers.ofByteArray(full))
.build();
var res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using var client = new HttpClient();
using var form = new MultipartFormDataContent();
var fileContent = new ByteArrayContent(File.ReadAllBytes("photo.heic"));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/octet-stream");
form.Add(fileContent, "file", "photo.heic");
form.Add(new StringContent("jpg"), "to_format");
var response = await client.PostAsync("https://cleverutils.com/api/v1/convert", form);
var json = await response.Content.ReadAsStringAsync();
var doc = JsonDocument.Parse(json).RootElement.GetProperty("data");
Console.WriteLine(doc.GetProperty("job_id").GetString());
Step 2: Read the response
The API returns a canonical JSON envelope with a job_id and links you can use to poll status and download the result.
{
"data": {
"job_id": "5f8c1a2e9d4b7c0e3f6a8b2d1e4c5f78",
"status": "done",
"output": {
"filename": "photo_cleverutils.com.jpg",
"size_bytes": 184523,
"size_human": "180 KB",
"url": "https://cleverutils.com/api/v1/jobs/5f8c.../output"
},
"links": {
"self": "https://cleverutils.com/api/v1/jobs/5f8c...",
"output": "https://cleverutils.com/api/v1/jobs/5f8c.../output"
},
"expires_at": "2026-04-10T14:32:11Z"
}
}
For small files, conversion runs synchronously and the response already contains status: "done". For larger files, you'll get status: "processing" and need to poll the job until it's ready.
Step 3: Download the result
Use the URL from data.output.url to download the converted file. The API streams the file as a binary attachment.
curl -o photo.jpg https://cleverutils.com/api/v1/jobs/5f8c.../output
Polling for async jobs
If your file is large or the response shows status: "processing", poll the job endpoint every few seconds until it's done.
import time, requests
while True:
s = requests.get(f"https://cleverutils.com/api/v1/jobs/{job_id}").json()["data"]
if s["status"] == "done":
break
if s["status"] == "error":
raise RuntimeError(s)
time.sleep(2)
out = requests.get(s["output"]["url"])
open("result.jpg", "wb").write(out.content)
Batch processing
To convert many files at once, use POST /api/v1/batch with up to 20 files in a single request.
curl -X POST https://cleverutils.com/api/v1/batch \
-F "files[][email protected]" \
-F "files[][email protected]" \
-F "files[][email protected]" \
-F "to_format=jpg"
Each file in the batch counts against your daily quota individually (max 20 files per request).
What's next?
- Full endpoint reference — every endpoint, every parameter, every response field.
- Rate limits — quotas, headers, and how not to get throttled.
- Error codes — every error you might see and how to recover.