API reference
Run 33 file tools from your own code. One key in a header, one upload, a status URL to poll — no SDK required.
Introduction
The Convertavo API exposes the same converters as the website — PDF, image, OCR and media tools — over plain HTTPS. It is organised around a few predictable resources, accepts multipart/form-data uploads, and always answers in JSON with standard HTTP status codes. Only want a tool on your own page, without writing code? The free QR code widget and text links are a copy and paste.
Authentication
Create a key in your dashboard under API keys. Keys start with at_ and are shown once: we store only a hash, so a lost key cannot be recovered — revoke it and create another. Send the key on every request:
Authorization: Bearer at_your_key_hereCONVERTAVO_KEY, as every example on this page does.
A missing key returns 401 Missing API key; a wrong or revoked key returns 401 Invalid or revoked API key.
Quickstart
Compress a PDF end to end: upload it, wait for the job, save the result. Pick your language — the choice is remembered across the page.
- 1Set your keySave it as the
CONVERTAVO_KEYenvironment variable. - 2What you needcURL, PHP with ext-curl, Python with
requests, or Node.js 18+. - 3Run the scriptIt uploads, polls every 2 seconds and downloads.
# 1. Upload — returns 202 and a status_url
curl -X POST https://convertavo.com/api/v1/tools/compress-pdf/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@report.pdf" \
-F "settings[Compression level]=Balanced (recommended)"
# 2. Poll until status is "done" or "failed"
curl https://convertavo.com/api/v1/conversions/852 \
-H "Authorization: Bearer $CONVERTAVO_KEY"
# 3. Download the signed download_url from step 2 as it is
# (no key needed; it stops working when the file is deleted)
curl -L -o report-small.pdf "https://convertavo.com/download/1814?expires=1791200000&signature=3f9a…"
<?php
const BASE = 'https://convertavo.com/api/v1';
function convertavo(string $method, string $url, array $fields = []): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY'), 'Accept: application/json'],
CURLOPT_POSTFIELDS => $fields ?: null,
]);
$body = json_decode(curl_exec($ch), true);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($code >= 400) {
throw new RuntimeException($body['message'] ?? "HTTP {$code}");
}
return $body;
}
// 1. Upload
$job = convertavo('POST', BASE . '/tools/compress-pdf/convert', [
'files[0]' => new CURLFile('report.pdf'),
'settings[Compression level]' => 'Balanced (recommended)',
]);
// 2. Poll
do {
sleep(2);
$job = convertavo('GET', $job['status_url'] ?? BASE . "/conversions/{$job['id']}");
} while (in_array($job['status'], ['queued', 'processing'], true));
if ($job['status'] === 'failed') {
throw new RuntimeException($job['error']);
}
// 3. Download
foreach ($job['files'] as $file) {
copy($file['download_url'], $file['name']);
}
import os
import time
import requests
BASE = 'https://convertavo.com/api/v1'
session = requests.Session()
session.headers['Authorization'] = f"Bearer {os.environ['CONVERTAVO_KEY']}"
# 1. Upload
with open('report.pdf', 'rb') as f:
r = session.post(
f'{BASE}/tools/compress-pdf/convert',
files=[('files[]', f)],
data={'settings[Compression level]': 'Balanced (recommended)'},
timeout=120,
)
r.raise_for_status()
job = r.json()
status_url = job['status_url']
# 2. Poll
while job['status'] in ('queued', 'processing'):
time.sleep(2)
job = session.get(status_url, timeout=30).json()
if job['status'] == 'failed':
raise RuntimeError(job['error'])
# 3. Download
for file in job['files']:
with open(file['name'], 'wb') as out:
out.write(requests.get(file['download_url'], timeout=120).content)
import { readFile, writeFile } from 'node:fs/promises';
const BASE = 'https://convertavo.com/api/v1';
const headers = { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
// 1. Upload
const form = new FormData();
form.append('files[]', new Blob([await readFile('report.pdf')]), 'report.pdf');
form.append('settings[Compression level]', 'Balanced (recommended)');
let res = await fetch(`${BASE}/tools/compress-pdf/convert`, { method: 'POST', headers, body: form });
let job = await res.json();
if (res.status !== 202) throw new Error(job.message);
const statusUrl = job.status_url;
// 2. Poll
while (['queued', 'processing'].includes(job.status)) {
await sleep(2000);
job = await (await fetch(statusUrl, { headers })).json();
}
if (job.status === 'failed') throw new Error(job.error);
// 3. Download
for (const file of job.files) {
const data = Buffer.from(await (await fetch(file.download_url)).arrayBuffer());
await writeFile(file.name, data);
}
How a conversion works
Conversions run in a queue, so the upload returns immediately and the work finishes in the background — usually within seconds.
status_urlqueued → processing → done or faileddownload_url, valid for one hour/api/v1/toolsList tools
Returns every active tool with the exact setting names its convert endpoint expects. Use it instead of hard-coding options. Tools with "api_supported": false work from a link and are website-only.
curl https://convertavo.com/api/v1/tools \
-H "Authorization: Bearer $CONVERTAVO_KEY"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
]);
$data = json_decode(curl_exec($ch), true);
print_r($data);
import os
import requests
response = requests.get(
'https://convertavo.com/api/v1/tools',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
timeout=30,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/tools', {
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
});
console.log(await response.json());
{
"tools": [
{
"key": "compress-pdf",
"name": "Compress PDF",
"category": "PDF",
"input": "file",
"api_supported": true,
"accepts": "PDF",
"accepts_multiple_files": false,
"settings": [ … ],
"switches": [ … ]
}
]
}
/api/v1/tools/{key}Retrieve a tool
One tool by its key. Returns 404 for an unknown or disabled tool.
| Path parameter | Type | Description |
|---|---|---|
key required | string | The tool key, e.g. compress-image. See the tool reference. |
curl https://convertavo.com/api/v1/tools/compress-image \
-H "Authorization: Bearer $CONVERTAVO_KEY"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/compress-image');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
]);
$data = json_decode(curl_exec($ch), true);
print_r($data);
import os
import requests
response = requests.get(
'https://convertavo.com/api/v1/tools/compress-image',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
timeout=30,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/tools/compress-image', {
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
});
console.log(await response.json());
{
"key": "compress-image",
"name": "Compress Image",
"category": "Image",
"input": "file",
"api_supported": true,
"accepts": "JPG, PNG, WebP",
"accepts_multiple_files": true,
"settings": [
{ "name": "Quality", "type": "range" },
{ "name": "Output format", "type": "select",
"options": ["Keep original", "JPG", "WebP", "AVIF"] },
{ "name": "Max width", "type": "number", "example": "1920" }
],
"switches": [
{ "index": 0, "name": "Strip EXIF and location data" }
]
}
/api/v1/tools/{key}/convertConvert files
Uploads one or more files and queues the conversion. Send multipart/form-data. Responds 202 Accepted straight away; follow status_url for the result.
| Field | Type | Description |
|---|---|---|
files[] required | file[] | Up to your plan's batch size per request (see Limits); split larger batches. Each file costs the tool's credits. Tools without multi-file support take one. |
settings[<name>] optional | string | A setting by its exact name, e.g. settings[Output format]=WebP. Omitted settings use the tool's default. |
switches[<index>] optional | boolean | A checkbox option by position; 1 on, 0 off. Omitted switches are off. |
curl -X POST https://convertavo.com/api/v1/tools/compress-image/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F 'settings[Quality]=80' \
-F 'settings[Output format]=WebP' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/compress-image/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'settings[Quality]' => '80',
'settings[Output format]' => 'WebP',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/compress-image/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb'))],
data={
'settings[Quality]': '80',
'settings[Output format]': 'WebP',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('settings[Quality]', '80');
form.append('settings[Output format]', 'WebP');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/compress-image/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
{
"id": 852,
"status": "queued",
"status_url": "https://convertavo.com/api/v1/conversions/852",
"credits_charged": 1,
"credits_remaining": 2999
}
/api/v1/conversions/{id}Retrieve a conversion
The state of a conversion you started. A conversion belonging to another account returns 404.
| Field | Type | Description |
|---|---|---|
status | enum | queued, processing, done or failed. |
error | string | null | When failed: a sentence safe to show to a person. |
files[] | array | When done: name, size in bytes, download_url. |
download_all_url | string | When a job produced several files: all of them as one ZIP. |
expires_at | ISO 8601 | When the output files are deleted. |
curl https://convertavo.com/api/v1/conversions/852 \
-H "Authorization: Bearer $CONVERTAVO_KEY"
<?php
$ch = curl_init('https://convertavo.com/api/v1/conversions/852');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
]);
$data = json_decode(curl_exec($ch), true);
print_r($data);
import os
import requests
response = requests.get(
'https://convertavo.com/api/v1/conversions/852',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
timeout=30,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/conversions/852', {
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
});
console.log(await response.json());
{
"id": 852,
"tool_key": "compress-image",
"status": "done",
"error": null,
"files": [
{
"name": "photo.webp",
"size": 58766,
"download_url": "https://convertavo.com/download/1814?expires=1794545280&signature=3f9a\u2026"
}
],
"expires_at": "2026-11-13T09:48:00+05:00"
}
Settings & switches
Options mirror the controls on each tool's page, so the names are the words you see there.
- Settings are addressed by label:
settings[Compression level]=Maximum compression. Spaces and punctuation are part of the name; your HTTP library encodes them. - Select settings accept one of the listed values exactly, including capitalisation.
- Switches are checkboxes by position, starting at
0. On the website the first switch starts ticked; over the API every switch is off unless you send1. - Unknown names are ignored rather than rejected — check spelling against Retrieve a tool if an option seems to have no effect.
Limits & quotas
| Limit | Free | Basic | Standard | Business | Lifetime |
|---|---|---|---|---|---|
| Credits | 50 a day | 3,000 a month | 10,000 a month | 40,000 a month | 60,000 once |
| Requests per minute | 10 | 30 | 60 | 120 | 60 |
| Largest file | 100 MB | 200 MB | 500 MB | 1,000 MB | 500 MB |
| Files per request | 10 | 20 | 50 | 100 | 50 |
- Conversions cost credits per file, at the same prices as the website and from the same balance: ten images at 1 credit each cost 10.
- Polling, listing tools and downloading results cost nothing, but every request counts towards your plan's requests per minute.
- Each convert response includes
credits_chargedandcredits_remainingso a script can pace itself. - Team members spend the team's credits.
Errors
Errors return a JSON body with a human-readable message. Validation errors (422) also include an errors object keyed by field.
| Status | Meaning | What to do |
|---|---|---|
| 202 | Accepted — the conversion is queued. | Poll status_url. |
| 401 | Missing, wrong or revoked key. | Check the Authorization header. |
| 404 | Unknown tool, or a conversion that is not yours. | Check the key or id. |
| 410 | A download link whose file has been deleted. | Convert again. |
| 422 | No file, a file over your plan's size limit, or a link-only tool. | Read errors and fix the request. |
| 402 | Not enough credits for this request. | The body has credits_needed, credits_remaining and credits_reset_at. Wait for the refill or upgrade. |
| 429 | Too many requests a minute for your plan. | Wait for the Retry-After seconds, then retry. |
| 5xx | Something failed on our side. | Retry with exponential backoff. |
{
"message": "The files field is required.",
"errors": {
"files": ["The files field is required."]
}
}
Downloads & retention
- Download URLs need no API key, so you can hand one to a browser. They are unguessable — treat them as secrets while they live.
- Input and output files are deleted one hour after conversion; afterwards the link returns
410. - The conversion record (name, size, status) stays in your dashboard history.
Best practices
done or failed.5xx and network errors with backoff. Don't retry 4xx — fix the request.Current limitations
- No webhooks yet — poll the status URL.
- No endpoint for conversion history; use the dashboard.
- Breaking changes will ship as
/v2;/v1stays as documented.
QR Code API
Make static and dynamic QR codes, download them as SVG, PNG, JPG, WebP or PDF, change where a dynamic code points, and read its scan analytics. It uses the same key as everything above, and the same engine as the website: the same payload formats, the same design rules, and the same check that a code actually scans before it is handed to you.
| Endpoint | What it does |
|---|---|
GET /api/v1/qr/types | Types you can use and the fields each one takes. |
POST /api/v1/qr/render | Make an image and return it. Nothing is saved. |
POST /api/v1/qr | Save a static or dynamic code. |
GET /api/v1/qr | List your codes (mode, status, type, q, per_page). |
GET · PATCH · DELETE /api/v1/qr/{id} | Read, change, or delete one code. id is the id returned when it was made. |
GET /api/v1/qr/{id}/image | The code as an image file. |
GET /api/v1/qr/{id}/analytics | Scans, unique visitors, devices, countries and more (dynamic only). |
403). Calls count towards your plan's requests per minute, and each key is limited to 60 requests a minute — 20 a minute for anything that renders an image. Going over returns 429 with a Retry-After header. Saving codes and dynamic codes follow the same plan allowances as the website.
dataholds the fields of the type. Ask/qr/typesfor the names — a WiFi code takesssid,password,authentication; a link takesurl.designis optional and every field of it is validated. Colours are#rrggbb; a design that wouldn't scan reliably is refused with422, itsscannability_scoreand thereasons.- Hosted pages (business card, menu, file pages…) and app-store links are made on the website, not through the API.
- A logo can be sent as a
data:image; SVG logos are cleaned of scripts, and anything that isn't really an image is refused. - A code that belongs to someone else answers
404, exactly as if it did not exist.
/api/v1/qr/renderRender a QR image
Send what the code should say; the image comes straight back. format is svg, png (default), jpg, webp or pdf; size is pixels, up to 2048.
curl -X POST https://convertavo.com/api/v1/qr/render \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"wifi","data":{"ssid":"Guest","password":"welcome-2026","authentication":"WPA"},"design":{"dots":{"shape":"rounded","color":{"type":"solid","value":"#1e3a8a"}}},"format":"png","size":512}' \
-o wifi.png
<?php
$ch = curl_init('https://convertavo.com/api/v1/qr/render');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['type' => 'wifi', 'data' => ['ssid' => 'Guest', 'password' => 'welcome-2026', 'authentication' => 'WPA'], 'design' => ['dots' => ['shape' => 'rounded', 'color' => ['type' => 'solid', 'value' => '#1e3a8a']]], 'format' => 'png', 'size' => 512]),
]);
$response = curl_exec($ch);
file_put_contents('wifi.png', $response);
import os
import requests
response = requests.request(
'POST',
'https://convertavo.com/api/v1/qr/render',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'type': 'wifi', 'data': {'ssid': 'Guest', 'password': 'welcome-2026', 'authentication': 'WPA'}, 'design': {'dots': {'shape': 'rounded', 'color': {'type': 'solid', 'value': '#1e3a8a'}}}, 'format': 'png', 'size': 512},
timeout=60,
)
response.raise_for_status()
open('wifi.png', 'wb').write(response.content)
import { writeFile } from 'node:fs/promises';
const response = await fetch('https://convertavo.com/api/v1/qr/render', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"type":"wifi","data":{"ssid":"Guest","password":"welcome-2026","authentication":"WPA"},"design":{"dots":{"shape":"rounded","color":{"type":"solid","value":"#1e3a8a"}}},"format":"png","size":512}),
});
if (!response.ok) throw new Error((await response.json()).message);
await writeFile('wifi.png', Buffer.from(await response.arrayBuffer()));
{
"message": "This design would not scan reliably, so it was not made.",
"scannability_score": 40,
"reasons": [
"Contrast between the dots and background is too low (1.4:1, minimum 4.0:1) …"
]
}
/api/v1/qrCreate a QR code
mode is static (default: the data is inside the code, forever) or dynamic (the code holds a short link you can re-point later — the type must produce a web address, like url). Optional: name, tags, folder_id, design, and for dynamic codes custom_slug. Returns 201.
curl -X POST https://convertavo.com/api/v1/qr \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"url","mode":"dynamic","name":"Spring menu","data":{"url":"https://example.com/menu"},"tags":["spring"]}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/qr');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['type' => 'url', 'mode' => 'dynamic', 'name' => 'Spring menu', 'data' => ['url' => 'https://example.com/menu'], 'tags' => ['spring']]),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'POST',
'https://convertavo.com/api/v1/qr',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'type': 'url', 'mode': 'dynamic', 'name': 'Spring menu', 'data': {'url': 'https://example.com/menu'}, 'tags': ['spring']},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/qr', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"type":"url","mode":"dynamic","name":"Spring menu","data":{"url":"https://example.com/menu"},"tags":["spring"]}),
});
console.log(await response.json());
{
"data": {
"id": "0f6c1b52-…",
"name": "Spring menu",
"type": "url",
"mode": "dynamic",
"status": "active",
"short_url": "https://…/q/k3d9x2p",
"destination_url": "https://example.com/menu",
"scannability_score": 100,
"scan_count": 0,
"links": {
"self": "https://…/api/v1/qr/0f6c1b52-…",
"image": "https://…/api/v1/qr/0f6c1b52-…/image",
"analytics": "https://…/api/v1/qr/0f6c1b52-…/analytics"
}
}
}
/api/v1/qr PATCH/api/v1/qr/{id}List, read, update, delete
PATCH accepts name, tags, and — for dynamic codes — destination (checked exactly like one typed on the website) and status (paused or active). The printed code never changes. DELETE returns 204.
curl -X PATCH https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000 \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"destination":"https://example.com/summer-menu"}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['destination' => 'https://example.com/summer-menu']),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'PATCH',
'https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'destination': 'https://example.com/summer-menu'},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"destination":"https://example.com/summer-menu"}),
});
console.log(await response.json());
{ "message": "That website is not allowed on this service." }
/api/v1/qr/{id}/image?format=svg&size=1024Download an image
Renders a saved code from its stored design. A dynamic code's image encodes its short link, so the file you print never needs to change.
curl -X GET https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/image?format=svg&size=1024 \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-o qr.svg
<?php
$ch = curl_init('https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/image?format=svg&size=1024');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
]);
$response = curl_exec($ch);
file_put_contents('qr.svg', $response);
import os
import requests
response = requests.request(
'GET',
'https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/image?format=svg&size=1024',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
timeout=60,
)
response.raise_for_status()
open('qr.svg', 'wb').write(response.content)
import { writeFile } from 'node:fs/promises';
const response = await fetch('https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/image?format=svg&size=1024', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error((await response.json()).message);
await writeFile('qr.svg', Buffer.from(await response.arrayBuffer()));
/api/v1/qr/{id}/analyticsScan analytics
range is today, 7d, 30d (default), 90d, 365d or custom with from and to dates. All dates are UTC. Visitors are counted, never identified.
curl -X GET https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/analytics?range=7d \
-H "Authorization: Bearer $CONVERTAVO_KEY"
<?php
$ch = curl_init('https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/analytics?range=7d');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'GET',
'https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/analytics?range=7d',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/qr/0f6c1b52-0000-0000-0000-000000000000/analytics?range=7d', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
});
console.log(await response.json());
{
"data": {
"from": "2026-09-16", "to": "2026-09-22",
"totals": { "scans": 412, "unique": 301 },
"delta": { "scans": 12.5, "unique": 8.1 },
"series": [ … ],
"breakdowns": { "device": [ … ], "country": [ … ], … }
}
}
Invoicing API
Create, issue and track invoices, quotes and credit notes; keep your clients; record payments; download the PDF and EN 16931-aligned XML; and have your own software told when an invoice is viewed, paid or overdue. It uses the same key as everything above, and the same engine as the Invoice Generator: the same validation, the same totals, the same numbering, the same PDF.
| Endpoint | What it does |
|---|---|
POST /api/v1/invoices | Create a draft, or issue it at once with "issue": true. |
GET /api/v1/invoices | List documents, newest first (status, type, client_id, issued_from, issued_to, q). |
GET · PATCH · DELETE /api/v1/invoices/{id} | Read a document; change or delete a draft. |
POST /api/v1/invoices/{id}/issue | Issue a draft: the number is assigned now. |
POST /api/v1/invoices/{id}/payments | Record a payment. |
POST /api/v1/invoices/{id}/send · /cancel | Email it to the client · cancel it. |
GET /api/v1/invoices/{id}/pdf | The PDF. |
GET /api/v1/invoices/{id}/pdf-a3 · /xml/ubl · /xml/cii | Structured data (invoices and credit notes). |
GET · POST /api/v1/clients, GET · PATCH · DELETE /api/v1/clients/{id} | Your clients. |
GET /api/v1/businesses | The businesses you invoice as. |
403). Issuing a document spends one credit, exactly as on the website; drafts, reading and payments are free. Requests a minute follow your plan, and going over returns 429 with Retry-After.
- Amounts are sent as decimal strings in the document's currency —
"1250.50", never a float. They come back twice: as decimal strings inamountsand as integers in minor units inamounts_minor; use the integers for arithmetic. The server works out every total itself; totals in a request are ignored. - Idempotency. Send an
Idempotency-Keyheader with anyPOST,PATCHorDELETE. The same key with the same body within 24 hours returns the first answer again, markedIdempotent-Replayed: true— so if a connection drops, just send it again. The same key with a different body is refused with422. - Pagination is by cursor: pass
meta.next_cursorback ascursoruntil it isnull.per_pagegoes up to 100. - An invoice or client that belongs to someone else answers
404, exactly as if it did not exist. - The full machine-readable reference: OpenAPI 3.1 document, generated from the code that serves these endpoints.
/api/v1/invoicesCreate & issue an invoice
The body is the invoice as the editor keeps it: currency, lines (each with description, quantity, unit_price, tax_rate, tax_category), and optionally dates, buyer, seller, discounts, shipping, notes and template. Give a client_id to fill in the buyer from a saved client, and a business_id to choose which of your businesses it comes from. With "issue": true it is numbered at once and the answer carries its pdf_url; without, it stays a draft you can change.
curl -X POST https://convertavo.com/api/v1/invoices \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"issue":true,"currency":"USD","issue_date":"2026-10-20","due_date":"2026-11-03","buyer":{"name":"Northwind Studio","email":"accounts@northwind.example"},"lines":[{"description":"Logo design","quantity":"1","unit_price":"450.00","tax_rate":"0","tax_category":"zero"},{"description":"Brand guidelines (hours)","quantity":"6","unit_price":"65.00","tax_rate":"0","tax_category":"zero"}],"notes":"Thank you for your business."}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/invoices');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['issue' => true, 'currency' => 'USD', 'issue_date' => '2026-10-20', 'due_date' => '2026-11-03', 'buyer' => ['name' => 'Northwind Studio', 'email' => 'accounts@northwind.example'], 'lines' => [['description' => 'Logo design', 'quantity' => '1', 'unit_price' => '450.00', 'tax_rate' => '0', 'tax_category' => 'zero'], ['description' => 'Brand guidelines (hours)', 'quantity' => '6', 'unit_price' => '65.00', 'tax_rate' => '0', 'tax_category' => 'zero']], 'notes' => 'Thank you for your business.']),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'POST',
'https://convertavo.com/api/v1/invoices',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'issue': True, 'currency': 'USD', 'issue_date': '2026-10-20', 'due_date': '2026-11-03', 'buyer': {'name': 'Northwind Studio', 'email': 'accounts@northwind.example'}, 'lines': [{'description': 'Logo design', 'quantity': '1', 'unit_price': '450.00', 'tax_rate': '0', 'tax_category': 'zero'}, {'description': 'Brand guidelines (hours)', 'quantity': '6', 'unit_price': '65.00', 'tax_rate': '0', 'tax_category': 'zero'}], 'notes': 'Thank you for your business.'},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/invoices', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"issue":true,"currency":"USD","issue_date":"2026-10-20","due_date":"2026-11-03","buyer":{"name":"Northwind Studio","email":"accounts@northwind.example"},"lines":[{"description":"Logo design","quantity":"1","unit_price":"450.00","tax_rate":"0","tax_category":"zero"},{"description":"Brand guidelines (hours)","quantity":"6","unit_price":"65.00","tax_rate":"0","tax_category":"zero"}],"notes":"Thank you for your business."}),
});
console.log(await response.json());
{
"data": {
"id": "0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f",
"object": "invoice",
"type": "invoice",
"status": "partially_paid",
"number": "INV-2026-0042",
"business_id": 12,
"client_id": 34,
"client_name": "Northwind Studio",
"currency": "USD",
"language": "en",
"issue_date": "2026-10-20",
"due_date": "2026-11-03",
"reference": "Project Kite",
"po_number": "PO-77",
"related_invoice_id": "0199a2b4-5d00-7e2f-a1b2-c3d4e5f60718",
"amounts": {
"subtotal": "840.00",
"discount": "0.00",
"shipping": "0.00",
"tax": "0.00",
"withholding": "0.00",
"rounding": "0.00",
"total": "840.00",
"payable": "840.00",
"paid": "400.00",
"credited": "0.00",
"balance": "440.00"
},
"amounts_minor": {
"subtotal": 84000,
"discount": 0,
"shipping": 0,
"tax": 0,
"withholding": 0,
"rounding": 0,
"total": 84000,
"payable": 84000,
"paid": 40000,
"credited": 0,
"balance": 44000
},
"issued_at": "2026-10-20T09:05:00+05:00",
"sent_at": "2026-10-20T09:06:00+05:00",
"viewed_at": "2026-10-20T11:30:00+05:00",
"paid_at": "2026-10-21T10:00:00+05:00",
"cancelled_at": "2026-10-21T10:00:00+05:00",
"pdf_url": "https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/pdf",
"pdf_sha256": "abababababababababababababababababababababababababababababababab",
"created_at": "2026-10-20T09:00:00+05:00",
"updated_at": "2026-10-21T10:00:00+05:00"
}
}
Mistakes come back as 422 with errors keyed by field path — lines.1.unit_price — so they can be shown next to the right field. Without credits left, issuing answers 402 and the draft is kept (draft_id).
/api/v1/invoices/{id}/paymentsInvoices, payments, sending
A payment is an amount (decimal string) or amount_minor, with paid_on, method and reference. Give your own external_id — a bank or gateway transaction id — and the same payment is recorded once however many times it arrives. The invoice moves to partially_paid and paid by itself.
curl -X POST https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/payments \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"amount":"300.00","method":"bank_transfer","reference":"TRX-9921","external_id":"bank-tx-55120"}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/payments');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['amount' => '300.00', 'method' => 'bank_transfer', 'reference' => 'TRX-9921', 'external_id' => 'bank-tx-55120']),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'POST',
'https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/payments',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'amount': '300.00', 'method': 'bank_transfer', 'reference': 'TRX-9921', 'external_id': 'bank-tx-55120'},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/payments', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"amount":"300.00","method":"bank_transfer","reference":"TRX-9921","external_id":"bank-tx-55120"}),
});
console.log(await response.json());
{
"data": { "id": 56, "object": "payment", "amount_minor": 30000, "amount": "300.00", "currency": "USD", "paid_on": "2026-10-21", "method": "bank_transfer", "reference": "TRX-9921" },
"invoice": { "id": "0199a2b4-…", "status": "partially_paid", "amounts_minor": { "payable": 84000, "paid": 30000, "balance": 54000, … }, … }
}
- An issued document cannot be changed or deleted (
409): correct it with a credit note from the website, or cancel it while nothing has been paid or credited. POST …/issueon a document that is already issued returns it unchanged — safe to repeat.POST …/sendemails the client the PDF and a private link, in the document's language;tooverrides the address.
/api/v1/clientsClients
Create with at least a name; add email, address, tax_number, country (ISO code), currency, language and payment_terms_days. PATCH changes only the fields you send (null clears one) and "archived": true archives. A client with documents cannot be deleted — archive it instead, and its history stays whole.
curl -X POST https://convertavo.com/api/v1/clients \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Northwind Studio","email":"accounts@northwind.example","country":"US","currency":"USD"}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/clients');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['name' => 'Northwind Studio', 'email' => 'accounts@northwind.example', 'country' => 'US', 'currency' => 'USD']),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'POST',
'https://convertavo.com/api/v1/clients',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'name': 'Northwind Studio', 'email': 'accounts@northwind.example', 'country': 'US', 'currency': 'USD'},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/clients', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"name":"Northwind Studio","email":"accounts@northwind.example","country":"US","currency":"USD"}),
});
console.log(await response.json());
/api/v1/invoices/{id}/pdf-a3PDF/A-3 & XML
For invoices and credit notes: the same document as structured data, aligned with EN 16931, the European e-invoicing standard. /xml/ubl is UBL 2.1, /xml/cii is UN/CEFACT CII, and /pdf-a3 is a PDF/A-3 copy of the PDF with the CII file inside it as factur-x.xml — one file that people read and accounting software imports.
409, because these formats have no place for it.
curl -X GET https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/xml/ubl \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-o invoice-ubl.xml
<?php
$ch = curl_init('https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/xml/ubl');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
]);
$response = curl_exec($ch);
file_put_contents('invoice-ubl.xml', $response);
import os
import requests
response = requests.request(
'GET',
'https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/xml/ubl',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
timeout=60,
)
response.raise_for_status()
open('invoice-ubl.xml', 'wb').write(response.content)
import { writeFile } from 'node:fs/promises';
const response = await fetch('https://convertavo.com/api/v1/invoices/0199a2b4-7a10-71d4-8c3b-1a2b3c4d5e6f/xml/ubl', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) throw new Error((await response.json()).message);
await writeFile('invoice-ubl.xml', Buffer.from(await response.arrayBuffer()));
Webhooks
Add an https address on the dashboard (Business tab → Webhooks) and choose the events. Each one is a POST of JSON — the event's type, a created time, and the invoice as data.object (plus data.payment on payment events):
invoice.issued, invoice.sent, invoice.viewed, invoice.reminder_sent, invoice.overdue, invoice.paid, invoice.cancelled, invoice.credited, payment.recorded, payment.refunded, quote.accepted, quote.declined
- Verify the signature.
Convertavo-Signatureist=<unix time>,v1=<signature>, where the signature is the hex HMAC-SHA256 of<t>.<raw body>under your endpoint's signing secret. Recompute it, compare in constant time, and refuse anything more than five minutes old. - Answer quickly. Any
2xxwithin 10 seconds is a delivery. Anything else is retried after a minute, five minutes, half an hour, two hours and six hours. An address that fails 15 deliveries in a row is switched off until you switch it back on. - Expect repeats.
Convertavo-Deliveryis the same on every retry of one delivery: keep the ones you have handled and skip them.
<?php
// $secret: the webhook's signing secret.
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_CONVERTAVO_SIGNATURE'] ?? '';
if (! preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header, $m)
|| abs(time() - (int) $m[1]) > 300
|| ! hash_equals(hash_hmac('sha256', $m[1].'.'.$body, $secret), $m[2])) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
// $event['type'], $event['data']['object'] …
http_response_code(200);
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the request body exactly as received (a Buffer).
export function verify(rawBody, header, secret) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header ?? '');
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${m[1]}.${rawBody}`)
.digest();
return timingSafeEqual(expected, Buffer.from(m[2], 'hex'));
}
Short links API
Shorten links, change where they go, and read their clicks — the same engine and the same safety checks as the URL shortener. It uses the same key as everything above.
| Endpoint | What it does |
|---|---|
POST /api/v1/links | Shorten one link. |
POST /api/v1/links/bulk | Shorten up to 100 links in one call. |
GET /api/v1/links | List your links (search, tag, per_page up to 100, page). |
GET · PATCH · DELETE /api/v1/links/{code} | Read, change or delete one link. |
GET /api/v1/links/{code}/stats | Clicks, unique visitors, countries, devices, referrers and more. |
429 with Retry-After), and new links follow the same hourly and daily limits as the website — 100 an hour and 1,000 a day per account.
- Every destination is checked exactly as on the website: it must be a public
http(s)address, not another shortener, not on the blocklist, and not listed by Google Safe Browsing. A refusal is422with amessageyou can show to a person. - Codes are case-insensitive. A link that belongs to someone else answers
404, exactly as if it did not exist. - A link blocked for safety can be read but not changed (
423). - Not available through RapidAPI: links belong to your account.
/api/v1/linksShorten a link
url is required. Optional: code (custom back-half, 3–40 letters, numbers, - or _), title, tags, expires_at and activates_at (ISO 8601, or local time with timezone), click_limit, one_time, password, expired_redirect_url, redirect_type (302 default or 301), og_title / og_description / og_image for the social preview, the five utm_* tags, and clean: true to strip tracking parameters such as fbclid. Returns 201.
curl -X POST https://convertavo.com/api/v1/links \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/spring-menu","code":"spring-menu","utm_source":"newsletter","utm_medium":"email","expires_at":"2026-12-31T23:59:00Z"}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/links');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['url' => 'https://example.com/spring-menu', 'code' => 'spring-menu', 'utm_source' => 'newsletter', 'utm_medium' => 'email', 'expires_at' => '2026-12-31T23:59:00Z']),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'POST',
'https://convertavo.com/api/v1/links',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'url': 'https://example.com/spring-menu', 'code': 'spring-menu', 'utm_source': 'newsletter', 'utm_medium': 'email', 'expires_at': '2026-12-31T23:59:00Z'},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/links', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"url":"https://example.com/spring-menu","code":"spring-menu","utm_source":"newsletter","utm_medium":"email","expires_at":"2026-12-31T23:59:00Z"}),
});
console.log(await response.json());
{
"code": "spring-menu",
"short_url": "https://…/s/spring-menu",
"destination": "https://example.com/spring-menu?utm_source=newsletter&utm_medium=email",
"safety_status": "clean",
"warnings": [],
"clicks": 0,
"is_active": true,
"redirect_type": 302,
"expires_at": "2026-12-31T23:59:00+00:00",
"urls": {
"qr_png": "https://…/tools/url-shortener/links/spring-menu/qr.png",
"qr_svg": "https://…/tools/url-shortener/links/spring-menu/qr.svg",
"stats": "https://…/tools/url-shortener/stats/spring-menu"
}
}
safety_status is clean, pending (Safe Browsing could not be asked yet; it is re-checked), or suspicious — the link works, but visitors see a warning first, and warnings says why.
/api/v1/links/{code} PATCH/api/v1/links/{code}List, read, update, delete
PATCH takes url (a new destination, checked like a new link — the short link and its QR code stay the same), any option above, remove_password: true, and is_active to pause or resume. DELETE returns 204; the address then answers 410 Gone and is never reused.
curl -X PATCH https://convertavo.com/api/v1/links/spring-menu \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com/summer-menu","is_active":true}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/links/spring-menu');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PATCH',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['url' => 'https://example.com/summer-menu', 'is_active' => true]),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'PATCH',
'https://convertavo.com/api/v1/links/spring-menu',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'url': 'https://example.com/summer-menu', 'is_active': True},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/links/spring-menu', {
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"url":"https://example.com/summer-menu","is_active":true}),
});
console.log(await response.json());
{ "message": "That is already a short link from another service. Shorten the address it leads to instead." }
/api/v1/links/bulkShorten in bulk
links is an array of up to 100 objects with the same fields as a single link. Each one succeeds or fails on its own; the answer lists them in order.
curl -X POST https://convertavo.com/api/v1/links/bulk \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-H "Content-Type: application/json" \
-d '{"links":[{"url":"https://example.com/a"},{"url":"https://example.com/b","code":"page-b"}]}'
<?php
$ch = curl_init('https://convertavo.com/api/v1/links/bulk');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CONVERTAVO_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['links' => [['url' => 'https://example.com/a'], ['url' => 'https://example.com/b', 'code' => 'page-b']]]),
]);
$response = curl_exec($ch);
print_r(json_decode($response, true));
import os
import requests
response = requests.request(
'POST',
'https://convertavo.com/api/v1/links/bulk',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
json={'links': [{'url': 'https://example.com/a'}, {'url': 'https://example.com/b', 'code': 'page-b'}]},
timeout=60,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/links/bulk', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CONVERTAVO_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({"links":[{"url":"https://example.com/a"},{"url":"https://example.com/b","code":"page-b"}]}),
});
console.log(await response.json());
{
"results": [
{ "index": 0, "link": { "code": "x7kq2m", "short_url": "https://…/s/x7kq2m", … } },
{ "index": 1, "error": "That back-half is already taken." }
]
}
/api/v1/links/{code}/statsClick statistics
range is 7d, 30d (default), 90d, 12m, all, or custom with from and to; grain is hour, day, week or month; bots=1 adds automated visits to clicks. Days are UTC. No cookies are used: unique visitors are counted per day from a value that changes daily, so they can never be traced to a person.
curl https://convertavo.com/api/v1/links/spring-menu/stats?range=7d \
-H "Authorization: Bearer $CONVERTAVO_KEY"
<?php
$ch = curl_init('https://convertavo.com/api/v1/links/spring-menu/stats?range=7d');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
]);
$data = json_decode(curl_exec($ch), true);
print_r($data);
import os
import requests
response = requests.get(
'https://convertavo.com/api/v1/links/spring-menu/stats?range=7d',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
timeout=30,
)
response.raise_for_status()
print(response.json())
const response = await fetch('https://convertavo.com/api/v1/links/spring-menu/stats?range=7d', {
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
});
console.log(await response.json());
{
"range": { "from": "2026-10-11", "to": "2026-10-17", "grain": "day", "includes_bots": false },
"totals": { "clicks": 412, "people": 412, "unique_daily": 330, "bots": 17, "qr_scans": 96, "all_time": 1288 },
"series": [ { "label": "Sun 11 Oct", "start": "2026-10-11", "clicks": 51 }, … ],
"breakdowns": {
"by_country": { "PK": 220, "AE": 81, "GB": 60, "other": 51 },
"by_device": { "mobile": 301, "desktop": 104, "tablet": 7 },
"by_source": { "link": 316, "qr": 96 }
}
}
Click webhooks
Add a webhook URL in Dashboard → Short links and every click on your links is sent to it as a JSON POST, a few seconds after it happens. Bots are not sent.
{
"event": "link.clicked",
"link": { "code": "spring-menu", "short_url": "https://…/s/spring-menu" },
"click": {
"at": "2026-10-17T12:04:55+00:00",
"country": "PK", "device": "mobile", "os": "Android", "browser": "Chrome",
"referrer": "www.instagram.com", "source": "link", "utm_campaign": "spring-sale"
}
}
- Each request carries
X-Convertavo-Signature: sha256=…, an HMAC-SHA256 of the raw body with your webhook's secret. Compute it yourself and compare before trusting the request. - Answer with any
2xxwithin 5 seconds. Anything else is retried after 10 seconds, 1 minute and 5 minutes. - After 20 failed deliveries in a row the webhook is switched off; switch it back on in the dashboard.
- Webhook addresses must be public
httpsURLs.
Tool reference
Every tool, its accepted files, all settings and a working request. Click a tool to expand it.
PDF 10 tools
Compress PDF
compress-pdf
Convertavo's Compress PDF tool shrinks a large PDF so it fits an email or an upload limit. Your text stays sharp. If a form requires you to submit a file that is smaller than 1 MB, you can type that in as your target size. You can see your new page compared to the old one, side by side, before downloading it.
/api/v1/tools/compress-pdf/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 5–15 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Compression level] optional |
string |
Balanced (recommended)Maximum compressionLight — best quality |
settings[Image quality] optional |
string |
Web (150 DPI)Print (300 DPI)Keep original |
settings[Target size (MB)] optional |
number |
Number. Example: 1080
|
switches[0] optional |
boolean | Downsample embedded images. Send 1 to turn on. |
switches[1] optional |
boolean | Remove metadata and leftover editing data. Send 1 to turn on. |
switches[2] optional |
boolean | Subset embedded fonts. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/compress-pdf/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Compression level]=Balanced (recommended)' \
-F 'settings[Image quality]=Web (150 DPI)' \
-F 'settings[Target size (MB)]=1080' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/compress-pdf/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Compression level]' => 'Balanced (recommended)',
'settings[Image quality]' => 'Web (150 DPI)',
'settings[Target size (MB)]' => '1080',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/compress-pdf/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Compression level]': 'Balanced (recommended)',
'settings[Image quality]': 'Web (150 DPI)',
'settings[Target size (MB)]': '1080',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Compression level]', 'Balanced (recommended)');
form.append('settings[Image quality]', 'Web (150 DPI)');
form.append('settings[Target size (MB)]', '1080');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/compress-pdf/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
JPG to PDF
jpg-to-pdf
Combine photos or scans into one PDF, with control over page size, orientation and margins. Free for files under 25 MB, and no account needed.
/api/v1/tools/jpg-to-pdf/convert
- Accepts
- JPG, PNG, HEIC
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 3–8 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (JPG, PNG, HEIC). Size limit depends on your plan. |
settings[Page size] optional |
string |
A4LetterFit to image |
settings[Orientation] optional |
string |
AutoPortraitLandscape |
settings[Margin] optional |
string |
NoneSmallMedium |
settings[Images per page] optional |
string |
124 |
switches[0] optional |
boolean | Compress images to reduce file size. Send 1 to turn on. |
switches[1] optional |
boolean | Add a page number to each page. Send 1 to turn on. |
switches[2] optional |
boolean | Sort files by name before combining. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/jpg-to-pdf/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F "files[]=@photo-2.jpg" \
-F 'settings[Page size]=A4' \
-F 'settings[Orientation]=Auto' \
-F 'settings[Margin]=None' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/jpg-to-pdf/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'files[1]' => new CURLFile('photo-2.jpg'),
'settings[Page size]' => 'A4',
'settings[Orientation]' => 'Auto',
'settings[Margin]' => 'None',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/jpg-to-pdf/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb')), ('files[]', open('photo-2.jpg', 'rb'))],
data={
'settings[Page size]': 'A4',
'settings[Orientation]': 'Auto',
'settings[Margin]': 'None',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('files[]', new Blob([await readFile('photo-2.jpg')]), basename('photo-2.jpg'));
form.append('settings[Page size]', 'A4');
form.append('settings[Orientation]', 'Auto');
form.append('settings[Margin]', 'None');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/jpg-to-pdf/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Merge PDF
merge-pdf
Upload your PDFs, rearrange them and download a combined file. Free, no account, no watermark.
/api/v1/tools/merge-pdf/convert
- Accepts
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 2–5 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (PDF). Size limit depends on your plan. |
settings[File order] optional |
string |
Manual (drag to reorder)Filename A–ZUpload order |
switches[0] optional |
boolean | Add a bookmark for each source file. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/merge-pdf/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F "files[]=@appendix.pdf" \
-F 'settings[File order]=Manual (drag to reorder)' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/merge-pdf/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'files[1]' => new CURLFile('appendix.pdf'),
'settings[File order]' => 'Manual (drag to reorder)',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/merge-pdf/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb')), ('files[]', open('appendix.pdf', 'rb'))],
data={
'settings[File order]': 'Manual (drag to reorder)',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('files[]', new Blob([await readFile('appendix.pdf')]), basename('appendix.pdf'));
form.append('settings[File order]', 'Manual (drag to reorder)');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/merge-pdf/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
PDF to Excel
pdf-to-excel
Convertavo's free PDF to Excel converter pulls the tables out of your PDF and puts them in a real spreadsheet. Rows and columns stay where they belong. It works on bank statements, invoices, price lists and reports, even when the table has no lines.
/api/v1/tools/pdf-to-excel/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 10–30 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Page range] optional |
string |
Text. Example: 1-3
|
settings[Output format] optional |
string |
Excel (.xlsx)CSV (.csv)Google Sheets |
settings[Header row] optional |
string |
Detect automaticallyFirst row is headerNo header |
settings[Decimal separator] optional |
string |
Point (1.50)Comma (1,50) |
switches[0] optional |
boolean | Keep empty rows. Send 1 to turn on. |
switches[1] optional |
boolean | Treat numbers as text. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/pdf-to-excel/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Page range]=1-3' \
-F 'settings[Output format]=Excel (.xlsx)' \
-F 'settings[Header row]=Detect automatically' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/pdf-to-excel/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Page range]' => '1-3',
'settings[Output format]' => 'Excel (.xlsx)',
'settings[Header row]' => 'Detect automatically',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/pdf-to-excel/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Page range]': '1-3',
'settings[Output format]': 'Excel (.xlsx)',
'settings[Header row]': 'Detect automatically',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Page range]', '1-3');
form.append('settings[Output format]', 'Excel (.xlsx)');
form.append('settings[Header row]', 'Detect automatically');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/pdf-to-excel/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
PDF to JPG
pdf-to-jpg
Turn each page into a JPG, PNG or WebP you can post, embed or edit in any image editor. Free, with no account and no watermark.
/api/v1/tools/pdf-to-jpg/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 4–12 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Page range] optional |
string |
Text. Example: 1-3
|
settings[Image format] optional |
string |
JPGPNGWebP |
settings[Resolution] optional |
string |
150 DPI (screen)300 DPI (print)600 DPI (archival) |
settings[Quality] optional |
integer |
Whole number, 1–100. Example: 80
|
switches[0] optional |
boolean | Deliver as a zip archive. Send 1 to turn on. |
switches[1] optional |
boolean | Use a white background instead of transparency. Send 1 to turn on. |
switches[2] optional |
boolean | Extract embedded images only. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/pdf-to-jpg/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Page range]=1-3' \
-F 'settings[Image format]=JPG' \
-F 'settings[Resolution]=150 DPI (screen)' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/pdf-to-jpg/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Page range]' => '1-3',
'settings[Image format]' => 'JPG',
'settings[Resolution]' => '150 DPI (screen)',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/pdf-to-jpg/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Page range]': '1-3',
'settings[Image format]': 'JPG',
'settings[Resolution]': '150 DPI (screen)',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Page range]', '1-3');
form.append('settings[Image format]', 'JPG');
form.append('settings[Resolution]', '150 DPI (screen)');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/pdf-to-jpg/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
PDF to Text
pdf-to-text
Upload a PDF and get clean, copyable text in seconds. Scanned pages go through OCR automatically. Free, with no account and no watermark.
/api/v1/tools/pdf-to-text/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 3–8 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Page range] optional |
string |
Text. Example: 1-3
|
settings[Output format] optional |
string |
Plain text (.txt)Word (.docx)Markdown (.md)Web page (.html)JSON (pages, lines, tables) |
settings[Text layout] optional |
string |
Keep line breaksMerge into paragraphsPreserve layout (columns & spacing)Single block of text |
settings[Language] optional |
string |
Auto-detectEnglishUrduArabicHindiChinese (Simplified)Chinese (Traditional)SpanishFrenchGermanPortugueseRussianJapaneseKoreanItalianTurkishPersian (Farsi)BengaliPunjabiPashtoSindhiNepaliMarathiGujaratiTamilTeluguIndonesianMalayVietnameseThaiFilipinoDutchPolishUkrainianSwedishCzechRomanianHungarianGreekHebrew |
settings[Document password] optional |
string |
Text. Example: your-pdf-password
|
settings[Translate to (optional, machine translation)] optional |
string |
Don't translateEnglishUrduArabicSpanishFrenchGermanHindiChineseRussianPortugueseTurkishItalian |
switches[0] optional |
boolean | Run OCR when no text layer is found. Send 1 to turn on. |
switches[1] optional |
boolean | Keep tables as tab-separated columns. Send 1 to turn on. |
switches[2] optional |
boolean | Remove headers and page numbers. Send 1 to turn on. |
switches[3] optional |
boolean | Mark where each page starts. Send 1 to turn on. |
switches[4] optional |
boolean | Fix words split across lines. Send 1 to turn on. |
switches[5] optional |
boolean | Re-read every page with OCR (fixes garbled text). Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/pdf-to-text/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Page range]=1-3' \
-F 'settings[Output format]=Plain text (.txt)' \
-F 'settings[Text layout]=Keep line breaks' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/pdf-to-text/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Page range]' => '1-3',
'settings[Output format]' => 'Plain text (.txt)',
'settings[Text layout]' => 'Keep line breaks',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/pdf-to-text/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Page range]': '1-3',
'settings[Output format]': 'Plain text (.txt)',
'settings[Text layout]': 'Keep line breaks',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Page range]', '1-3');
form.append('settings[Output format]', 'Plain text (.txt)');
form.append('settings[Text layout]', 'Keep line breaks');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/pdf-to-text/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
PDF to Word
pdf-to-word
Convertavo's free PDF to Word converter turns your PDF into a real Word file. Keep every page looking exactly the same, or get the words as paragraphs you can edit. Scanned PDFs work too: their pages are read with OCR as part of the conversion.
/api/v1/tools/pdf-to-word/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 8–20 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Page range] optional |
string |
Text. Example: 1-3
|
settings[Output] optional |
string |
Word (.docx)OpenDocument (.odt)Rich text (.rtf) |
settings[Layout mode] optional |
string |
Same layout, editableKeep exact layoutFlowing textText only |
settings[Picture quality] optional |
string |
High (200 DPI)Very high (300 DPI)Smaller file (150 DPI) |
settings[Language] optional |
string |
Auto-detectEnglishUrduArabicHindiChinese (Simplified)Chinese (Traditional)SpanishFrenchGermanPortugueseRussianJapaneseKoreanItalianTurkishPersian (Farsi)BengaliPunjabiPashtoSindhiNepaliMarathiGujaratiTamilTeluguIndonesianMalayVietnameseThaiFilipinoDutchPolishUkrainianSwedishCzechRomanianHungarianGreekHebrew |
settings[Document password] optional |
string |
Text. Example: your-pdf-password
|
settings[Translate to (optional, machine translation)] optional |
string |
Don't translateEnglishUrduArabicSpanishFrenchGermanHindiChineseRussianPortugueseTurkishItalian |
switches[0] optional |
boolean | Run OCR on scanned pages. Send 1 to turn on. |
switches[1] optional |
boolean | Keep images and graphics. Send 1 to turn on. |
switches[2] optional |
boolean | Convert tables to Word tables. Send 1 to turn on. |
switches[3] optional |
boolean | Keep fonts, sizes and colours. Send 1 to turn on. |
switches[4] optional |
boolean | Leave out repeated headers, footers and page numbers. Send 1 to turn on. |
switches[5] optional |
boolean | Start each PDF page on a new Word page. Send 1 to turn on. |
switches[6] optional |
boolean | Add a table of contents from the headings. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/pdf-to-word/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Page range]=1-3' \
-F 'settings[Output]=Word (.docx)' \
-F 'settings[Layout mode]=Same layout, editable' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/pdf-to-word/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Page range]' => '1-3',
'settings[Output]' => 'Word (.docx)',
'settings[Layout mode]' => 'Same layout, editable',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/pdf-to-word/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Page range]': '1-3',
'settings[Output]': 'Word (.docx)',
'settings[Layout mode]': 'Same layout, editable',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Page range]', '1-3');
form.append('settings[Output]', 'Word (.docx)');
form.append('settings[Layout mode]', 'Same layout, editable');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/pdf-to-word/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Rotate PDF
rotate-pdf
Convertavo's free tool lets you rotate PDF pages online and save the change for good. Turn the whole file, one page, or only the pages that came out wrong. It can even spot sideways and upside-down pages by itself.
/api/v1/tools/rotate-pdf/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 2–4 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Rotation] optional |
string |
90° clockwise90° counter-clockwise180° |
settings[Apply to] optional |
string |
All pagesOdd pages onlyEven pages onlySelected pages |
settings[Selected pages] optional |
string |
Text. Example: 3, 7
|
switches[0] optional |
boolean | Detect page orientation automatically. Send 1 to turn on. |
switches[1] optional |
boolean | Keep the original file as a copy. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/rotate-pdf/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Rotation]=90° clockwise' \
-F 'settings[Apply to]=All pages' \
-F 'settings[Selected pages]=3, 7' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/rotate-pdf/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Rotation]' => '90° clockwise',
'settings[Apply to]' => 'All pages',
'settings[Selected pages]' => '3, 7',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/rotate-pdf/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Rotation]': '90° clockwise',
'settings[Apply to]': 'All pages',
'settings[Selected pages]': '3, 7',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Rotation]', '90° clockwise');
form.append('settings[Apply to]', 'All pages');
form.append('settings[Selected pages]', '3, 7');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/rotate-pdf/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Split PDF
split-pdf
Convertavo's free tool breaks a PDF into separate files online. Grab a single page, pull out pages 3 to 7, or cut a long report into parts by typing the page ranges you want. Think of it as a PDF cutter that lives in your browser. You don't have to install anything.
/api/v1/tools/split-pdf/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 2–6 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Pages to extract] optional |
string |
Text. Example: 1-5, 12
|
settings[Filename prefix] optional |
string |
Text. Example: part
|
Example request
curl -X POST https://convertavo.com/api/v1/tools/split-pdf/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Pages to extract]=1-5, 12' \
-F 'settings[Filename prefix]=part'
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/split-pdf/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Pages to extract]' => '1-5, 12',
'settings[Filename prefix]' => 'part',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/split-pdf/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Pages to extract]': '1-5, 12',
'settings[Filename prefix]': 'part',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Pages to extract]', '1-5, 12');
form.append('settings[Filename prefix]', 'part');
const response = await fetch('https://convertavo.com/api/v1/tools/split-pdf/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Unlock PDF
unlock-pdf
Convertavo's free tool removes the password from a PDF, so the file opens without asking every time. Lots of banks lock statements with your date of birth, and typing it in every month gets old fast. Put it in here one time. What you download is a plain PDF that you can open, print, merge or convert like any other file.
/api/v1/tools/unlock-pdf/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 2–5 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Document password] optional |
string |
Text. Example: your-pdf-password
|
settings[Remove] optional |
string |
BothOpen passwordPermissions only |
switches[0] optional |
boolean | Allow printing after unlocking. Send 1 to turn on. |
switches[1] optional |
boolean | Allow copying text after unlocking. Send 1 to turn on. |
switches[2] optional |
boolean | Allow editing after unlocking. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/unlock-pdf/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Document password]=your-pdf-password' \
-F 'settings[Remove]=Both' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/unlock-pdf/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Document password]' => 'your-pdf-password',
'settings[Remove]' => 'Both',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/unlock-pdf/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Document password]': 'your-pdf-password',
'settings[Remove]': 'Both',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Document password]', 'your-pdf-password');
form.append('settings[Remove]', 'Both');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/unlock-pdf/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Image 7 tools
Compress Image
compress-image
Convertavo's free image compressor makes photos smaller without making them blurry. Most images come out 60 to 80% lighter, and you won't see a difference. Add JPG, PNG or WebP files up to 25 MB, pick a quality, and download.
/api/v1/tools/compress-image/convert
- Accepts
- JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 4–10 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF). Size limit depends on your plan. |
settings[Compression] optional |
string |
Smart — no visible differenceMaximum — smallest fileLossless — pixel-perfectCustom qualityTarget file size |
settings[Quality] optional |
integer |
Whole number, 1–100. Example: 80
|
settings[Target size (KB)] optional |
number |
Number. Example: 200
|
settings[Output format] optional |
string |
Keep originalSmallest (auto)JPGPNGWebPAVIF |
settings[Resize] optional |
string |
Keep original sizeMax 3840 px (4K)Max 2560 pxMax 1920 px (Full HD)Max 1280 pxMax 1080 pxMax 800 pxScale to 75%Scale to 50%Scale to 25%Custom width and height |
settings[Max width (px)] optional |
number |
Number. Example: 1920
|
settings[Max height (px)] optional |
number |
Number. Example: 1080
|
settings[Background for transparency] optional |
string |
WhiteBlack |
settings[File names] optional |
string |
Keep original namesAdd "-compressed"Add "-min" |
switches[0] optional |
boolean | Strip EXIF and location data. Send 1 to turn on. |
switches[1] optional |
boolean | Convert PNG photos to JPG when smaller. Send 1 to turn on. |
switches[2] optional |
boolean | Keep the colour profile. Send 1 to turn on. |
switches[3] optional |
boolean | Keep animation (GIF and WebP). Send 1 to turn on. |
switches[4] optional |
boolean | Never make a file bigger. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/compress-image/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F "files[]=@photo-2.jpg" \
-F 'settings[Compression]=Smart — no visible difference' \
-F 'settings[Quality]=80' \
-F 'settings[Target size (KB)]=200' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/compress-image/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'files[1]' => new CURLFile('photo-2.jpg'),
'settings[Compression]' => 'Smart — no visible difference',
'settings[Quality]' => '80',
'settings[Target size (KB)]' => '200',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/compress-image/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb')), ('files[]', open('photo-2.jpg', 'rb'))],
data={
'settings[Compression]': 'Smart — no visible difference',
'settings[Quality]': '80',
'settings[Target size (KB)]': '200',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('files[]', new Blob([await readFile('photo-2.jpg')]), basename('photo-2.jpg'));
form.append('settings[Compression]', 'Smart — no visible difference');
form.append('settings[Quality]', '80');
form.append('settings[Target size (KB)]', '200');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/compress-image/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Crop Image
crop-image
Need a photo square for Instagram, or tall for a story? Convertavo's free crop tool lets you drag a box over the part you want to keep, right in your browser. Most crops are done in 2 to 5 seconds.
/api/v1/tools/crop-image/convert
- Accepts
- JPG, PNG, WebP
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 2–5 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (JPG, PNG, WebP). Size limit depends on your plan. |
settings[Aspect ratio] optional |
string |
Free1:1 square16:94:33:29:16 story |
settings[Width (px)] optional |
number |
Number. Example: 1080
|
settings[Height (px)] optional |
number |
Number. Example: 1080
|
settings[Anchor] optional |
string |
CentreTopBottomLeftRight |
switches[0] optional |
boolean | Apply the same crop to every image. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/crop-image/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F "files[]=@photo-2.jpg" \
-F 'settings[Aspect ratio]=Free' \
-F 'settings[Width (px)]=1080' \
-F 'settings[Height (px)]=1080' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/crop-image/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'files[1]' => new CURLFile('photo-2.jpg'),
'settings[Aspect ratio]' => 'Free',
'settings[Width (px)]' => '1080',
'settings[Height (px)]' => '1080',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/crop-image/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb')), ('files[]', open('photo-2.jpg', 'rb'))],
data={
'settings[Aspect ratio]': 'Free',
'settings[Width (px)]': '1080',
'settings[Height (px)]': '1080',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('files[]', new Blob([await readFile('photo-2.jpg')]), basename('photo-2.jpg'));
form.append('settings[Aspect ratio]', 'Free');
form.append('settings[Width (px)]', '1080');
form.append('settings[Height (px)]', '1080');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/crop-image/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
HEIC to JPG
heic-to-jpg
Someone sent you a photo from their iPhone and your Windows PC won't open it? Convertavo's free HEIC to JPG converter turns it into a normal JPG that every phone, computer and website can open. Drop in one photo or a whole folder. It usually takes 3 to 7 seconds.
/api/v1/tools/heic-to-jpg/convert
- Accepts
- HEIC, HEIF
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 3–7 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (HEIC, HEIF). Size limit depends on your plan. |
settings[JPG quality] optional |
integer |
Whole number, 1–100. Example: 80
|
settings[Output format] optional |
string |
JPGPNGWebP |
settings[Max dimension] optional |
number |
Number. Example: 2048
|
switches[0] optional |
boolean | Keep EXIF data such as date and camera. Send 1 to turn on. |
switches[1] optional |
boolean | Keep GPS location data. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/heic-to-jpg/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@IMG_0412.heic" \
-F "files[]=@IMG_0413.heic" \
-F 'settings[JPG quality]=80' \
-F 'settings[Output format]=JPG' \
-F 'settings[Max dimension]=2048' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/heic-to-jpg/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('IMG_0412.heic'),
'files[1]' => new CURLFile('IMG_0413.heic'),
'settings[JPG quality]' => '80',
'settings[Output format]' => 'JPG',
'settings[Max dimension]' => '2048',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/heic-to-jpg/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('IMG_0412.heic', 'rb')), ('files[]', open('IMG_0413.heic', 'rb'))],
data={
'settings[JPG quality]': '80',
'settings[Output format]': 'JPG',
'settings[Max dimension]': '2048',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('IMG_0412.heic')]), basename('IMG_0412.heic'));
form.append('files[]', new Blob([await readFile('IMG_0413.heic')]), basename('IMG_0413.heic'));
form.append('settings[JPG quality]', '80');
form.append('settings[Output format]', 'JPG');
form.append('settings[Max dimension]', '2048');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/heic-to-jpg/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
PNG to JPG
png-to-jpg
Your phone saves every screenshot as a PNG, and those files pile up fast. Convertavo's free PNG to JPG converter turns them into much smaller JPGs that look the same. In our example, a 596 KB screenshot came out at 162 KB. Add one file or a whole folder, and it usually takes 2 to 6 seconds.
/api/v1/tools/png-to-jpg/convert
- Accepts
- PNG
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 2–6 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (PNG). Size limit depends on your plan. |
settings[JPG quality] optional |
integer |
Whole number, 1–100. Example: 80
|
settings[Background for transparency] optional |
string |
WhiteBlackCustom colour |
settings[Custom colour] optional |
string |
Text. Example: #F5E6CC
|
settings[Max width] optional |
number |
Number. Example: 1920
|
switches[0] optional |
boolean | Strip metadata. Send 1 to turn on. |
switches[1] optional |
boolean | Optimise for the web. Send 1 to turn on. |
switches[2] optional |
boolean | Keep the colour profile. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/png-to-jpg/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@image.png" \
-F "files[]=@image-2.png" \
-F 'settings[JPG quality]=80' \
-F 'settings[Background for transparency]=White' \
-F 'settings[Custom colour]=#F5E6CC' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/png-to-jpg/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('image.png'),
'files[1]' => new CURLFile('image-2.png'),
'settings[JPG quality]' => '80',
'settings[Background for transparency]' => 'White',
'settings[Custom colour]' => '#F5E6CC',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/png-to-jpg/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('image.png', 'rb')), ('files[]', open('image-2.png', 'rb'))],
data={
'settings[JPG quality]': '80',
'settings[Background for transparency]': 'White',
'settings[Custom colour]': '#F5E6CC',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('image.png')]), basename('image.png'));
form.append('files[]', new Blob([await readFile('image-2.png')]), basename('image-2.png'));
form.append('settings[JPG quality]', '80');
form.append('settings[Background for transparency]', 'White');
form.append('settings[Custom colour]', '#F5E6CC');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/png-to-jpg/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Remove Background
remove-background
Want just the person or the product, without the messy room behind it? Convertavo's free background remover cuts out the subject and hands you a clean transparent PNG. No brushes and no tracing by hand. It usually takes 8 to 20 seconds.
/api/v1/tools/remove-background/convert
- Accepts
- JPG, PNG, WebP
- Files per request
- One (send one file per request)
- Typical time
- 8–20 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (JPG, PNG, WebP). Size limit depends on your plan. |
settings[Output] optional |
string |
Transparent PNGWhite backgroundCustom colour |
settings[Custom colour] optional |
string |
Text. Example: #F5E6CC
|
settings[Edge refinement] optional |
string |
StandardSoft (hair and fur)Sharp (products) |
switches[0] optional |
boolean | Keep the original resolution. Send 1 to turn on. |
switches[1] optional |
boolean | Add a soft shadow under the subject. Send 1 to turn on. |
switches[2] optional |
boolean | Crop tightly to the subject. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/remove-background/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F 'settings[Output]=Transparent PNG' \
-F 'settings[Custom colour]=#F5E6CC' \
-F 'settings[Edge refinement]=Standard' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/remove-background/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'settings[Output]' => 'Transparent PNG',
'settings[Custom colour]' => '#F5E6CC',
'settings[Edge refinement]' => 'Standard',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/remove-background/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb'))],
data={
'settings[Output]': 'Transparent PNG',
'settings[Custom colour]': '#F5E6CC',
'settings[Edge refinement]': 'Standard',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('settings[Output]', 'Transparent PNG');
form.append('settings[Custom colour]', '#F5E6CC');
form.append('settings[Edge refinement]', 'Standard');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/remove-background/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Resize Image
resize-image
Set an exact width and height, or scale by percentage. You can also aim for a print size or a KB limit. Aspect ratio is locked by default.
/api/v1/tools/resize-image/convert
- Accepts
- JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 3–8 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF). Size limit depends on your plan. |
settings[Resize by] optional |
string |
Exact size (width × height)Popular size (preset)PercentageLongest sidePrint size (cm, mm or inches)File size (KB) |
settings[Preset size] optional |
string |
Instagram · Square post — 1080 × 1080Instagram · Portrait post — 1080 × 1350Instagram · Landscape post — 1080 × 566Instagram · Story or Reel — 1080 × 1920Facebook · Post — 1200 × 630Facebook · Cover photo — 851 × 315Facebook · Profile picture — 320 × 320X (Twitter) · Post — 1600 × 900X (Twitter) · Header — 1500 × 500LinkedIn · Post — 1200 × 627LinkedIn · Banner — 1584 × 396YouTube · Thumbnail — 1280 × 720YouTube · Channel banner — 2560 × 1440TikTok · Video cover — 1080 × 1920Pinterest · Pin — 1000 × 1500WhatsApp · Profile photo — 500 × 500WhatsApp · Status — 1080 × 1920Passport photo · 2 × 2 in (US) — 600 × 600Passport photo · 35 × 45 mm — 413 × 531Website · Blog image — 1200 × 800Email header — 600 × 200App icon or favicon — 512 × 512HD — 1280 × 720Full HD — 1920 × 10804K UHD — 3840 × 2160A4 page at 300 DPI — 2480 × 3508 |
settings[Width (px)] optional |
number |
Number. Example: 1920
|
settings[Height (px)] optional |
number |
Number. Example: 1080
|
settings[Percentage (%)] optional |
number |
Number. Example: 50
|
settings[Longest side (px)] optional |
number |
Number. Example: 1920
|
settings[Print width] optional |
number |
Number. Example: 10
|
settings[Print height] optional |
number |
Number. Example: 15
|
settings[Unit] optional |
string |
cmmminches |
settings[Target file size (KB)] optional |
number |
Number. Example: 200
|
settings[Resize mode] optional |
string |
Fit insideFill and cropPad to exact sizeStretch |
settings[Crop focus] optional |
string |
Smart — find the subjectCentreTopBottomLeftRight |
settings[Padding] optional |
string |
WhiteBlackTransparentBlurred pictureAverage colour |
settings[Output format] optional |
string |
Keep originalJPGPNGWebPAVIF |
settings[Quality] optional |
integer |
Whole number, 1–100. Example: 80
|
settings[Resampling] optional |
string |
Lanczos — sharpest, for photosBicubic — smoothBilinear — softestNearest — pixel art and icons |
settings[Print resolution (DPI)] optional |
string |
Keep original7296150300600 |
settings[File names] optional |
string |
Keep original namesAdd "-resized"Add the new size (e.g. -1080x1080) |
switches[0] optional |
boolean | Lock aspect ratio. Send 1 to turn on. |
switches[1] optional |
boolean | Do not enlarge images that are already smaller. Send 1 to turn on. |
switches[2] optional |
boolean | Sharpen after resizing. Send 1 to turn on. |
switches[3] optional |
boolean | Strip EXIF and location data. Send 1 to turn on. |
switches[4] optional |
boolean | Keep animation (GIF and WebP). Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/resize-image/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F "files[]=@photo-2.jpg" \
-F 'settings[Resize by]=Exact size (width × height)' \
-F 'settings[Preset size]=Instagram · Square post — 1080 × 1080' \
-F 'settings[Width (px)]=1920' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/resize-image/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'files[1]' => new CURLFile('photo-2.jpg'),
'settings[Resize by]' => 'Exact size (width × height)',
'settings[Preset size]' => 'Instagram · Square post — 1080 × 1080',
'settings[Width (px)]' => '1920',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/resize-image/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb')), ('files[]', open('photo-2.jpg', 'rb'))],
data={
'settings[Resize by]': 'Exact size (width × height)',
'settings[Preset size]': 'Instagram · Square post — 1080 × 1080',
'settings[Width (px)]': '1920',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('files[]', new Blob([await readFile('photo-2.jpg')]), basename('photo-2.jpg'));
form.append('settings[Resize by]', 'Exact size (width × height)');
form.append('settings[Preset size]', 'Instagram · Square post — 1080 × 1080');
form.append('settings[Width (px)]', '1920');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/resize-image/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
WebP Converter
webp-converter
Saved a picture from a website and it came out as a .webp file? Convertavo's free WebP converter turns it into a JPG or PNG that opens anywhere. It works the other way too, turning JPG and PNG files into small WebP images for your website in 2 to 5 seconds.
/api/v1/tools/webp-converter/convert
- Accepts
- JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 2–5 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF). Size limit depends on your plan. |
settings[Convert to] optional |
string |
WebPJPGPNGAVIFGIF (keeps animation)ICO (favicon)TIFFBMP |
settings[Compression] optional |
string |
Smart — no visible differenceMaximum — smallest fileLossless — pixel-perfectCustom qualityTarget file size |
settings[Quality] optional |
integer |
Whole number, 1–100. Example: 80
|
settings[Target size (KB)] optional |
number |
Number. Example: 200
|
settings[Resize] optional |
string |
Keep original sizeMax 3840 px (4K)Max 2560 pxMax 1920 px (Full HD)Max 1280 pxMax 1080 pxMax 800 pxScale to 75%Scale to 50%Scale to 25%Custom width and height |
settings[Max width (px)] optional |
number |
Number. Example: 1920
|
settings[Max height (px)] optional |
number |
Number. Example: 1080
|
settings[Background for transparency] optional |
string |
WhiteBlack |
settings[WebP effort] optional |
string |
BalancedFastSmallest file (slower) |
settings[File names] optional |
string |
Keep original namesAdd "-compressed"Add "-min" |
switches[0] optional |
boolean | Keep EXIF data such as date and camera. Send 1 to turn on. |
switches[1] optional |
boolean | Lossless — no quality loss, larger file. Send 1 to turn on. |
switches[2] optional |
boolean | Keep the colour profile. Send 1 to turn on. |
switches[3] optional |
boolean | Keep animation (GIF and WebP). Send 1 to turn on. |
switches[4] optional |
boolean | Keep exact colours under transparency. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/webp-converter/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F "files[]=@photo-2.jpg" \
-F 'settings[Convert to]=WebP' \
-F 'settings[Compression]=Smart — no visible difference' \
-F 'settings[Quality]=80' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/webp-converter/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'files[1]' => new CURLFile('photo-2.jpg'),
'settings[Convert to]' => 'WebP',
'settings[Compression]' => 'Smart — no visible difference',
'settings[Quality]' => '80',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/webp-converter/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb')), ('files[]', open('photo-2.jpg', 'rb'))],
data={
'settings[Convert to]': 'WebP',
'settings[Compression]': 'Smart — no visible difference',
'settings[Quality]': '80',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('files[]', new Blob([await readFile('photo-2.jpg')]), basename('photo-2.jpg'));
form.append('settings[Convert to]', 'WebP');
form.append('settings[Compression]', 'Smart — no visible difference');
form.append('settings[Quality]', '80');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/webp-converter/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
OCR 3 tools
Image to Excel
image-to-excel
Upload a photo, screenshot or scanned page with a table on it and get a real spreadsheet, each value in its own cell. Free, no account needed for files up to 25 MB.
/api/v1/tools/image-to-excel/convert
- Accepts
- JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF
- Files per request
- One (send one file per request)
- Typical time
- 3–10 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (JPG, PNG, WebP, HEIC, AVIF, GIF, BMP, TIFF). Size limit depends on your plan. |
settings[Language] optional |
string |
Auto-detectEnglishUrduArabicChinese |
settings[Output format] optional |
string |
Excel (.xlsx)CSV (.csv) |
settings[Header row] optional |
string |
Detect automaticallyFirst row is headerNo header |
settings[Decimal separator] optional |
string |
Point (1.50)Comma (1,50) |
switches[0] optional |
boolean | Merge cells that span multiple columns. Send 1 to turn on. |
switches[1] optional |
boolean | Keep empty rows. Send 1 to turn on. |
switches[2] optional |
boolean | Treat numbers as text. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/image-to-excel/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F 'settings[Language]=Auto-detect' \
-F 'settings[Output format]=Excel (.xlsx)' \
-F 'settings[Header row]=Detect automatically' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/image-to-excel/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'settings[Language]' => 'Auto-detect',
'settings[Output format]' => 'Excel (.xlsx)',
'settings[Header row]' => 'Detect automatically',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/image-to-excel/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb'))],
data={
'settings[Language]': 'Auto-detect',
'settings[Output format]': 'Excel (.xlsx)',
'settings[Header row]': 'Detect automatically',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('settings[Language]', 'Auto-detect');
form.append('settings[Output format]', 'Excel (.xlsx)');
form.append('settings[Header row]', 'Detect automatically');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/image-to-excel/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Image to Text
image-to-text
Upload a photo, screenshot or scan and get selectable text you can copy, edit and search. Free, no account needed for files up to 25 MB.
/api/v1/tools/image-to-text/convert
- Accepts
- JPG, PNG, HEIC, WebP, TIFF, BMP, GIF
- Files per request
- Up to your plan’s batch size (5 on Free, up to 100 on Business)
- Typical time
- 5–15 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file[] | One or more files (JPG, PNG, HEIC, WebP, TIFF, BMP, GIF). Size limit depends on your plan. |
settings[Language] optional |
string |
Auto-detectEnglishUrduArabicHindiChinese (Simplified)Chinese (Traditional)SpanishFrenchGermanPortugueseRussianJapaneseKoreanItalianTurkishPersian (Farsi)BengaliPunjabiPashtoSindhiNepaliMarathiGujaratiTamilTeluguIndonesianMalayVietnameseThaiFilipinoDutchPolishUkrainianSwedishCzechRomanianHungarianGreekHebrew |
settings[Second language (mixed text)] optional |
string |
NoneEnglishUrduArabicHindiChinese (Simplified)Chinese (Traditional)SpanishFrenchGermanPortugueseRussianJapaneseKoreanItalianTurkishPersian (Farsi)BengaliPunjabiPashtoSindhiNepaliMarathiGujaratiTamilTeluguIndonesianMalayVietnameseThaiFilipinoDutchPolishUkrainianSwedishCzechRomanianHungarianGreekHebrew |
settings[Image mode] optional |
string |
AutoPrinted documentScreenshotPhoto of a pageHandwritingSign or sparse textSingle lineMath formula (LaTeX) |
settings[Layout] optional |
string |
Keep line breaksMerge into paragraphsPreserve layout (columns & spacing)Single block of text |
settings[Output format] optional |
string |
Plain text (.txt)Word (.docx)Searchable PDFMarkdown (.md)JSON (words, positions, confidence) |
settings[Translate to (optional, machine translation)] optional |
string |
Don't translateEnglishUrduArabicSpanishFrenchGermanHindiChineseRussianPortugueseTurkishItalian |
switches[0] optional |
boolean | Enhance contrast before reading. Send 1 to turn on. |
switches[1] optional |
boolean | Correct page skew automatically. Send 1 to turn on. |
switches[2] optional |
boolean | Flag low-confidence words. Send 1 to turn on. |
switches[3] optional |
boolean | Clean up stray symbols and noise. Send 1 to turn on. |
switches[4] optional |
boolean | Combine a batch into one file. Send 1 to turn on. |
switches[5] optional |
boolean | Detect QR codes and barcodes. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/image-to-text/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg" \
-F "files[]=@photo-2.jpg" \
-F 'settings[Language]=Auto-detect' \
-F 'settings[Second language (mixed text)]=None' \
-F 'settings[Image mode]=Auto' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/image-to-text/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
'files[1]' => new CURLFile('photo-2.jpg'),
'settings[Language]' => 'Auto-detect',
'settings[Second language (mixed text)]' => 'None',
'settings[Image mode]' => 'Auto',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/image-to-text/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb')), ('files[]', open('photo-2.jpg', 'rb'))],
data={
'settings[Language]': 'Auto-detect',
'settings[Second language (mixed text)]': 'None',
'settings[Image mode]': 'Auto',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
form.append('files[]', new Blob([await readFile('photo-2.jpg')]), basename('photo-2.jpg'));
form.append('settings[Language]', 'Auto-detect');
form.append('settings[Second language (mixed text)]', 'None');
form.append('settings[Image mode]', 'Auto');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/image-to-text/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
PDF OCR
pdf-ocr
Convertavo's PDF OCR turns a scanned PDF into a searchable PDF. Upload your file, pick the language, and download a copy you can search, select and copy from.
/api/v1/tools/pdf-ocr/convert
- Accepts
- Files per request
- One (send one file per request)
- Typical time
- 10–40 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PDF). Size limit depends on your plan. |
settings[Language] optional |
string |
Auto-detectEnglishSpanishUrduArabicChinese |
settings[Page range] optional |
string |
Text. Example: 1-3
|
settings[Output] optional |
string |
Searchable PDF (same look)Searchable PDF + text file |
Example request
curl -X POST https://convertavo.com/api/v1/tools/pdf-ocr/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@document.pdf" \
-F 'settings[Language]=Auto-detect' \
-F 'settings[Page range]=1-3' \
-F 'settings[Output]=Searchable PDF (same look)'
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/pdf-ocr/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('document.pdf'),
'settings[Language]' => 'Auto-detect',
'settings[Page range]' => '1-3',
'settings[Output]' => 'Searchable PDF (same look)',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/pdf-ocr/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('document.pdf', 'rb'))],
data={
'settings[Language]': 'Auto-detect',
'settings[Page range]': '1-3',
'settings[Output]': 'Searchable PDF (same look)',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('document.pdf')]), basename('document.pdf'));
form.append('settings[Language]', 'Auto-detect');
form.append('settings[Page range]', '1-3');
form.append('settings[Output]', 'Searchable PDF (same look)');
const response = await fetch('https://convertavo.com/api/v1/tools/pdf-ocr/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Media 3 tools
Compress Video
compress-video
Video too big to email, or WhatsApp won't send it? Convertavo's free video compressor shrinks it to the size you need without turning it to mush. Upload an MP4, MOV, MKV or AVI, type the size you want, and most videos are done in 1 to 4 minutes.
/api/v1/tools/compress-video/convert
- Accepts
- MP4, MOV, MKV, AVI
- Files per request
- One (send one file per request)
- Typical time
- 60–240 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (MP4, MOV, MKV, AVI). Size limit depends on your plan. |
settings[Compression level] optional |
string |
Balanced (recommended)MaximumLight — best quality |
settings[Target size (MB)] optional |
number |
Number. Example: 25
|
settings[Resolution] optional |
string |
Keep original1080p720p480p |
settings[Codec] optional |
string |
H.264H.265 |
switches[0] optional |
boolean | Reduce the frame rate to 30 fps. Send 1 to turn on. |
switches[1] optional |
boolean | Lower the audio bitrate to 128 kbps. Send 1 to turn on. |
switches[2] optional |
boolean | Remove audio entirely. Send 1 to turn on. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/compress-video/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@video.mp4" \
-F 'settings[Compression level]=Balanced (recommended)' \
-F 'settings[Target size (MB)]=25' \
-F 'settings[Resolution]=Keep original' \
-F "switches[0]=1"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/compress-video/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('video.mp4'),
'settings[Compression level]' => 'Balanced (recommended)',
'settings[Target size (MB)]' => '25',
'settings[Resolution]' => 'Keep original',
'switches[0]' => '1',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/compress-video/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('video.mp4', 'rb'))],
data={
'settings[Compression level]': 'Balanced (recommended)',
'settings[Target size (MB)]': '25',
'settings[Resolution]': 'Keep original',
'switches[0]': '1',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('video.mp4')]), basename('video.mp4'));
form.append('settings[Compression level]', 'Balanced (recommended)');
form.append('settings[Target size (MB)]', '25');
form.append('settings[Resolution]', 'Keep original');
form.append('switches[0]', '1');
const response = await fetch('https://convertavo.com/api/v1/tools/compress-video/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
MOV to MP4
mov-to-mp4
Your iPhone video plays fine on the phone, then won't open on a Windows PC. Convertavo's free MOV to MP4 converter fixes that in one step, with nothing to install. Upload the MOV, keep the default settings, and most videos are ready in 30 to 120 seconds.
/api/v1/tools/mov-to-mp4/convert
- Accepts
- MOV, AVI, MKV, WebM
- Files per request
- One (send one file per request)
- Typical time
- 30–120 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (MOV, AVI, MKV, WebM). Size limit depends on your plan. |
settings[Quality] optional |
string |
Same as source1080p720p480p |
settings[Video codec] optional |
string |
H.264 (most compatible)H.265 (smaller files) |
Example request
curl -X POST https://convertavo.com/api/v1/tools/mov-to-mp4/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@video.mp4" \
-F 'settings[Quality]=Same as source' \
-F 'settings[Video codec]=H.264 (most compatible)'
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/mov-to-mp4/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('video.mp4'),
'settings[Quality]' => 'Same as source',
'settings[Video codec]' => 'H.264 (most compatible)',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/mov-to-mp4/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('video.mp4', 'rb'))],
data={
'settings[Quality]': 'Same as source',
'settings[Video codec]': 'H.264 (most compatible)',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('video.mp4')]), basename('video.mp4'));
form.append('settings[Quality]', 'Same as source');
form.append('settings[Video codec]', 'H.264 (most compatible)');
const response = await fetch('https://convertavo.com/api/v1/tools/mov-to-mp4/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
MP4 to MP3
mp4-to-mp3
Only want the sound from a video, like a lecture for the bus or a song from a concert clip? Convertavo's free MP4 to MP3 converter keeps the audio and drops the picture. Upload the video, pick the quality, and download your MP3, usually in under a minute.
/api/v1/tools/mp4-to-mp3/convert
- Accepts
- MP4, MOV, MKV, AVI
- Files per request
- One (send one file per request)
- Typical time
- 20–60 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (MP4, MOV, MKV, AVI). Size limit depends on your plan. |
settings[Bitrate] optional |
string |
192 kbps (recommended)320 kbps128 kbps |
settings[Channels] optional |
string |
StereoMono |
Example request
curl -X POST https://convertavo.com/api/v1/tools/mp4-to-mp3/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@video.mp4" \
-F 'settings[Bitrate]=192 kbps (recommended)' \
-F 'settings[Channels]=Stereo'
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/mp4-to-mp3/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('video.mp4'),
'settings[Bitrate]' => '192 kbps (recommended)',
'settings[Channels]' => 'Stereo',
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/mp4-to-mp3/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('video.mp4', 'rb'))],
data={
'settings[Bitrate]': '192 kbps (recommended)',
'settings[Channels]': 'Stereo',
},
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('video.mp4')]), basename('video.mp4'));
form.append('settings[Bitrate]', '192 kbps (recommended)');
form.append('settings[Channels]', 'Stereo');
const response = await fetch('https://convertavo.com/api/v1/tools/mp4-to-mp3/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
AI 2 tools
AI Art Detector
ai-art-detector
Found a drawing online and something about it feels off? Convertavo's free AI art detector checks whether a drawing, painting or illustration came out of an AI image generator. You don't just get a score, you see the evidence. Upload a JPG, PNG or WebP, and the answer usually takes 2 to 4 seconds.
/api/v1/tools/ai-art-detector/convert
- Accepts
- JPG, PNG, WebP
- Files per request
- One (send one file per request)
- Typical time
- 2–4 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (JPG, PNG, WebP). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/ai-art-detector/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/ai-art-detector/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/ai-art-detector/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
const response = await fetch('https://convertavo.com/api/v1/tools/ai-art-detector/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
AI Image Detector
ai-image-detector
Upload a picture, and Convertavo's AI image detector estimates whether it was generated. It's an estimate, not proof. You get a verdict, how sure the model is, and the evidence behind it. When the honest answer is "can't tell", the report says so.
/api/v1/tools/ai-image-detector/convert
- Accepts
- JPG, PNG, WebP
- Files per request
- One (send one file per request)
- Typical time
- 2–4 seconds
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (JPG, PNG, WebP). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/ai-image-detector/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@photo.jpg"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/ai-image-detector/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('photo.jpg'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/ai-image-detector/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('photo.jpg', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('photo.jpg')]), basename('photo.jpg'));
const response = await fetch('https://convertavo.com/api/v1/tools/ai-image-detector/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Links 5 tools
Bulk URL Shortener
bulk-url-shortener
Need to shorten a whole list of links? Paste them into Convertavo’s bulk URL shortener, one per line, or upload a CSV file. Every short link comes back in one spreadsheet, each with its own QR code. It’s free, and for up to 50 links at a time you don’t even need an account.
/api/v1/tools/bulk-url-shortener/convert
- Accepts
- Any file
- Files per request
- One (send one file per request)
- Typical time
- a minute or two
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (any type). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/bulk-url-shortener/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@file.bin"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/bulk-url-shortener/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('file.bin'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/bulk-url-shortener/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('file.bin', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('file.bin')]), basename('file.bin'));
const response = await fetch('https://convertavo.com/api/v1/tools/bulk-url-shortener/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Link Expander
expand-url
Not sure about a short link someone sent you? Drop it into Convertavo’s short URL checker, and you’ll see the real address before you ever tap it. We follow the link for you and show the full address, every stop on the way and a safety check. No account, no app.
/api/v1/tools/expand-url/convert
- Accepts
- Any file
- Files per request
- One (send one file per request)
- Typical time
- instant
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (any type). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/expand-url/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@file.bin"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/expand-url/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('file.bin'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/expand-url/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('file.bin', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('file.bin')]), basename('file.bin'));
const response = await fetch('https://convertavo.com/api/v1/tools/expand-url/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Link in Bio Page
link-in-bio
Your profile has room for one link, but you have five things to share. Make a free link in bio page on Convertavo, and that one link opens all of them, with the designs, videos, sign-up forms and insights other services charge for. No ads.
/api/v1/tools/link-in-bio/convert
- Accepts
- Any file
- Files per request
- One (send one file per request)
- Typical time
- instant
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (any type). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/link-in-bio/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@file.bin"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/link-in-bio/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('file.bin'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/link-in-bio/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('file.bin', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('file.bin')]), basename('file.bin'));
const response = await fetch('https://convertavo.com/api/v1/tools/link-in-bio/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
URL Shortener
url-shortener
Is your link so long it breaks in a text message? Paste it into Convertavo’s free URL shortener, and you’ll get a short one back in about a second. Every link comes with a QR code and a click counter. No account. Nothing sits behind a paid plan, either.
/api/v1/tools/url-shortener/convert
- Accepts
- Any file
- Files per request
- One (send one file per request)
- Typical time
- instant
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (any type). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/url-shortener/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@file.bin"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/url-shortener/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('file.bin'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/url-shortener/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('file.bin', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('file.bin')]), basename('file.bin'));
const response = await fetch('https://convertavo.com/api/v1/tools/url-shortener/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
UTM Link Builder
utm-builder
Did that newsletter actually bring anyone to your site, or was it the Instagram post? Run the link through Convertavo’s UTM builder before you share it, and your analytics will finally tell you. The tagged link comes out long and ugly (that’s normal), so shorten it right here. No account needed, and it costs nothing.
/api/v1/tools/utm-builder/convert
- Accepts
- Any file
- Files per request
- One (send one file per request)
- Typical time
- instant
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (any type). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/utm-builder/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@file.bin"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/utm-builder/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('file.bin'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/utm-builder/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('file.bin', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('file.bin')]), basename('file.bin'));
const response = await fetch('https://convertavo.com/api/v1/tools/utm-builder/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
Business 1 tool
Invoice Generator
invoice-generator
Make a professional invoice in your client's language and currency. Taxes, discounts and withholding added up exactly, and a PDF with no watermark — no account needed.
/api/v1/tools/invoice-generator/convert
- Accepts
- Any file
- Files per request
- One (send one file per request)
- Typical time
- instant
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (any type). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/invoice-generator/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@file.bin"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/invoice-generator/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('file.bin'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/invoice-generator/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('file.bin', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('file.bin')]), basename('file.bin'));
const response = await fetch('https://convertavo.com/api/v1/tools/invoice-generator/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
QR Code 2 tools
QR Code Generator
qr-code-generator
Make a QR code for a link, Wi-Fi network, contact card or WhatsApp chat. Pick the colours, dot shape, frame and logo, then download it.
/api/v1/tools/qr-code-generator/convert
- Accepts
- Any file
- Files per request
- One (send one file per request)
- Typical time
- instant
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (any type). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/qr-code-generator/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@file.bin"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/qr-code-generator/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('file.bin'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/qr-code-generator/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('file.bin', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('file.bin')]), basename('file.bin'));
const response = await fetch('https://convertavo.com/api/v1/tools/qr-code-generator/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);
QR Code Scanner
qr-code-scanner
Free QR code and barcode scanner. Scan from your camera, a photo, a screenshot or a PDF, and every link is checked before you open it.
/api/v1/tools/qr-code-scanner/convert
- Accepts
- PNG, JPG, WebP, GIF, PDF
- Files per request
- One (send one file per request)
- Typical time
- instant
- Try it
- Open the tool page →
Request body multipart/form-data
| Field | Type | Description |
|---|---|---|
files[] required |
file | The file to convert (PNG, JPG, WebP, GIF, PDF). Size limit depends on your plan. |
Example request
curl -X POST https://convertavo.com/api/v1/tools/qr-code-scanner/convert \
-H "Authorization: Bearer $CONVERTAVO_KEY" \
-F "files[]=@image.png"
<?php
$ch = curl_init('https://convertavo.com/api/v1/tools/qr-code-scanner/convert');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAVO_KEY')],
CURLOPT_POSTFIELDS => [
'files[0]' => new CURLFile('image.png'),
],
]);
$job = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status !== 202) {
throw new RuntimeException($job['message'] ?? "Request failed ({$status})");
}
echo $job['status_url'];
import os
import requests
response = requests.post(
'https://convertavo.com/api/v1/tools/qr-code-scanner/convert',
headers={'Authorization': f"Bearer {os.environ['CONVERTAVO_KEY']}"},
files=[('files[]', open('image.png', 'rb'))],
timeout=120,
)
response.raise_for_status()
job = response.json()
print(job['status_url'])
import { readFile } from 'node:fs/promises';
import { basename } from 'node:path';
const form = new FormData();
form.append('files[]', new Blob([await readFile('image.png')]), basename('image.png'));
const response = await fetch('https://convertavo.com/api/v1/tools/qr-code-scanner/convert', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAVO_KEY}` },
body: form,
});
const job = await response.json();
if (response.status !== 202) throw new Error(job.message);
console.log(job.status_url);