Polling and downloading
Poll a job until it finishes, then fetch its outputs through short-lived signed URLs.
Poll the job
The API has no webhooks. After you start a job, read it until status is succeeded, failed or cancelled:
const finished = ["succeeded", "failed", "cancelled"];
let job = await call(`/v1/jobs/${id}/start`, { method: "POST" });
while (!finished.includes(job.status)) {
await new Promise((resolve) => setTimeout(resolve, 1000));
job = await call(`/v1/jobs/${id}`);
}
Poll about once a second for a single job. Every poll counts toward the 120 requests a minute your key gets, which is 2 a second across all your jobs. With N jobs running at once, poll each one every N seconds or slower: 4 jobs every 4 seconds, 10 jobs every 10 seconds. That keeps polling at 1 request a second and leaves room for the create, start and download calls.
Give up after a deadline you choose. A conversion attempt runs for at most 10 minutes, and a retry can follow, so 11 minutes covers almost every job. If you stop waiting, cancel the job to release its reservation.
Download the outputs
When the job has succeeded, ask for its download URLs:
curl https://convt-api-production.up.railway.app/v1/jobs/$JOB/download \
-H "Authorization: Bearer $CONVT_API_KEY"
{
"outputs": [{ "name": "input.webp", "url": "https://…" }],
"expires_in": 300
}
outputshas one entry per file. Most conversions produce one. Some produce several, such as a PDF converted to PNG, which gives one image per page.nameis the output’s file name. It is always based oninput, not on the name of the file you uploaded:input.webp, orinput.png,input-2.png,input-3.pngfor pages. Replaceinputwith your own name when you save it.urlis a signedGETURL. It needs noAuthorizationheader.expires_inis how many seconds the URLs stay valid, at most 300. Call the route again for fresh URLs.
You can call download as often as you like until the job expires 24 hours after creation. It never charges again.
Calling download before the job succeeds returns 400 not_ready.
Save the files
import { writeFile } from "node:fs/promises";
const { outputs } = await call(`/v1/jobs/${id}/download`);
for (const output of outputs) {
const res = await fetch(output.url);
if (!res.ok) throw new Error(`download failed with ${res.status}`);
const name = output.name.replace(/^input/, "photo"); // input-2.png -> photo-2.png
await writeFile(name, Buffer.from(await res.arrayBuffer()));
}curl -sS "$API/v1/jobs/$JOB/download" -H "$AUTH" |
jq -r '.outputs[] | "\(.name)\t\(.url)"' |
while IFS=$'\t' read -r NAME URL; do curl -sS -o "${NAME/#input/photo}" "$URL"; doneTo hand a file to a browser, return the signed url and link to it. A plain link or redirect works from any site. Fetching the URL with JavaScript from your own origin does not, because storage only allows cross-origin reads from convt.app.