API
Waiting and retries
A job runs in the background. Wait for it in the request, poll it or stream it, and send a request again safely with an idempotency key.
Wait in the request
Prefer: wait=N holds the answer to POST /v1/jobs for up to N seconds, 60 at most. When the job finishes in time the answer is 200 with the finished job. Otherwise it is 202 with the job as it is, and Location names it.
An answer that waited says so in Preference-Applied: wait=N.
Without Prefer, the answer is 202 at once.
$ export CV_API_KEY=cv_sk_…Set your key once. Every cv command reads it.
$ curl -X POST https://api.convert.dev/v1/jobs \ -H "Authorization: Bearer $CV_API_KEY" \ -H "Content-Type: application/json" \ -H "Prefer: wait=30" \ -d '{"inputs": ["file_01JC9W3TQ6V8ZK2N5R7M4H1XEB"], "to": "pdf"}'response7 lines
{ "object": "job", "id": "job_01JC9W5D3K8N2Q6T9V1M4R7XAE", "status": "running", "progress": 0.4, …}$ curl https://api.convert.dev/v1/jobs/job_01JC9W5D3K8N2Q6T9V1M4R7XAE \ -H "Authorization: Bearer $CV_API_KEY"Stream
With Accept: text/event-stream, GET /v1/jobs/{id} streams a job event each time the job changes. Each event carries the whole job, and the stream ends when the job finishes. An event's id names the job as it was then. Open the stream again with that id in Last-Event-ID, and it skips the job until it changes.
A project can have 20 streams open at once, and one more fails with concurrency_limit. A stream that drops, such as during a deploy, can be opened again.
$ curl -N https://api.convert.dev/v1/jobs/job_01JC9W5D3K8N2Q6T9V1M4R7XAE \ -H "Authorization: Bearer $CV_API_KEY" \ -H "Accept: text/event-stream"response7 lines
id: job_01JC9W5D3K8N2Q6T9V1M4R7XAEevent: jobdata: {"object":"job","id":"job_01JC9W5D3K8N2Q6T9V1M4R7XAE","status":"running","progress":0.4,…} id: job_01JC9W5D3K8N2Q6T9V1M4R7XAEevent: jobdata: {"object":"job","id":"job_01JC9W5D3K8N2Q6T9V1M4R7XAE","status":"done","progress":1.0,…}Send it again safely
A request that timed out may have started a job or made a file. POST /v1/jobs and POST /v1/files take Idempotency-Key with a value of your own, up to 255 bytes. A retry with the same key and the same request within 24 hours answers 200 with the first job or file, instead of making and paying for a new one.
For a file sent as the body, the bytes are part of the request. A retried request for upload instructions answers with fresh instructions while the file is still uploading. The same key with a different request fails with conflict. cv sends a key of its own, so running a command again waits for the job it started.
$ curl -X POST https://api.convert.dev/v1/jobs \ -H "Authorization: Bearer $CV_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: lease-pdf-2026-11-03" \ -d '{"inputs": ["file_01JC9W3TQ6V8ZK2N5R7M4H1XEB"], "to": "pdf"}'Which errors to retry
retryable in an error says whether the same request can work later. These are rate_limited, concurrency_limit, not_ready, timeout, engine_failed and internal. Retry once after a short wait, and report the request-id or the job id if it fails again.
rate_limited and concurrency_limit answer 429 with Retry-After, the seconds to wait: 1 for rate_limited, and 5 for concurrency_limit, which waits for a job to end. The other codes need a change first, to the request, the file or the account.
Every answer to a request with a key says where the project stands: RateLimit-Limit is 300, RateLimit-Remaining the requests left now, RateLimit-Reset the seconds until all 300 are back, and RateLimit-Policy is 300;w=10. Requests come back at 30 a second.
Updated 3 November 2026