Skip to content
convt
Esc
↑↓navigate↵open⌘Jpreview
On this page

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
}
  • outputs has one entry per file. Most conversions produce one. Some produce several, such as a PDF converted to PNG, which gives one image per page.
  • name is the output’s file name. It is always based on input, not on the name of the file you uploaded: input.webp, or input.png, input-2.png, input-3.png for pages. Replace input with your own name when you save it.
  • url is a signed GET URL. It needs no Authorization header.
  • expires_in is 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"; done

To 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.