Public API
Convert files programmatically using an API key.
Getting started
- Go to Organization → Settings → API Keys and create a key. The full value is shown once — copy it now.
- Submit a job:
curl -X POST https://convertere.io/v1/jobs \
-H "Authorization: Bearer cvio_live_..." \
-H "Content-Type: application/json" \
-d '{
"tasks": {
"import-1": { "operation": "import/upload" },
"convert-1": {
"operation": "convert",
"input": "import-1",
"input_format": "png",
"output_format": "webp"
},
"export-1": { "operation": "export/url", "input": "convert-1" }
}
}'- Upload your file to the
uploadUrlreturned on theimport-1task, using aPUTrequest with the file as the body. - Poll
GET /v1/jobs/{id}untilstatusisfinished, or register a webhook and wait for the notification. - Download the result from the
urlon theexport-1task.
Authentication
Every request (except GET /v1/openapi.json) needs a header:
Authorization: Bearer cvio_live_...or equivalently X-API-Key: cvio_live_.... Keys come in two modes:
live— real conversions, debits your organization's credit balance.test— the full flow runs (status transitions, webhooks, a downloadable result), but no worker is ever contacted and nothing is charged. Use this to build and rehearse your integration before switching to a live key.
A missing, revoked, or expired key returns 401 with no distinguishing signal between those cases.
The task DAG
Requests use a minimal task graph — one import/upload, one convert, one export/url, chained by name:
{
"tasks": {
"import-1": { "operation": "import/upload" },
"convert-1": {
"operation": "convert",
"input": "import-1",
"input_format": "png",
"output_format": "webp",
"options": { "quality": 80 }
},
"export-1": { "operation": "export/url", "input": "convert-1" }
},
"tag": "optional-client-reference"
}tag is an optional string echoed back on every status response and webhook payload, for correlating a job with your own records. options passes through unchanged to the target format's conversion options.
This is deliberately not a byte-identical clone of any other provider's API — response fields, status values, and errors follow this platform's own conventions.
Job status
GET /v1/jobs/{id} returns:
{
"id": "...",
"status": "processing",
"mode": "single",
"testMode": false,
"credits_estimated": 3,
"tasks": [
{ "name": "import-1", "operation": "import/upload", "status": "finished" },
{ "name": "convert-1", "operation": "convert", "status": "processing" },
{ "name": "export-1", "operation": "export/url", "status": "waiting" }
],
"createdAt": "2026-07-31T12:00:00Z"
}status is one of waiting, processing, finished, error. Once finished, credits_used and the export-1 task's result.url are present. Polling this endpoint also opportunistically advances the job the moment your upload is detected — you don't need to call anything else after uploading.
Capability catalog
GET /v1/operations lists every currently supported conversion with an estimated cost:
{
"operations": [
{
"input_format": "png",
"output_format": "webp",
"group": "image",
"credits_estimated": 1
}
]
}Webhooks
Register an endpoint from the dashboard to receive job.finished / job.failed notifications instead of polling:
{
"event": "job.finished",
"job": { "...": "same shape as GET /v1/jobs/{id}" },
"sentAt": "2026-07-31T12:05:00Z"
}Each delivery is signed:
X-Webhook-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed by your endpoint's secret>Verify it (Node example):
import { createHmac, timingSafeEqual } from "node:crypto";
function isValid(rawBody, header, secret) {
const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
return timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}Failed deliveries retry up to 5 times over roughly 80 minutes. Your job's true status via GET /v1/jobs/{id} is always authoritative, independent of whether a webhook ever successfully delivered.
Errors
{ "error": { "code": "insufficient_credits", "message": "..." } }| Status | Meaning |
|---|---|
| 400 | Malformed request, or the source/target format pair isn't supported |
| 401 | Missing, invalid, revoked, or expired API key |
| 402 | Organization balance can't cover this job's maximum cost |
| 404 | Job not found, or not owned by this specific key |
| 409 | Idempotency-Key reused with a different request body |
| 429 | Rate limit or per-key concurrency cap exceeded — see Retry-After |
Rate limits
60 requests/minute per API key, and at most 3 jobs in progress per key at once. Both scope to the individual key, not the organization — an org with multiple keys effectively gets a multiple of each limit.
Idempotency
Pass an Idempotency-Key header on POST /v1/jobs to safely retry after a network failure. The same key with the same request body returns the original job instead of creating a new one; the same key with a different body returns 409.