Jobs
A job runs one model on one ready file. You create it, poll it until it finishes, and download the result.
Create a job
POST /v1/audio/denoise with the file id and, optionally, the model (default: the task's default model, today zilapi/nr-4.1). It answers 202 Accepted with the Job object and a Location: /v1/jobs/{id} header. Send an Idempotency-Key so a retry never creates a second job.
With a live key we hold the job's exact cost from your credit at once. If your available credit is lower, you get 402 insufficient_credit and nothing starts.
curl -s https://api.zilapi.com/v1/audio/denoise -H "Authorization: Bearer $ZILAPI_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"file": "'"$FILE_ID"'", "model": "zilapi/nr-4.1"}' \
> job.json
JOB_ID=$(jq -r .id job.json)Lifecycle
queued → processing → succeeded or failed. A queued job can also become canceled. succeeded, failed and canceled are final.
| Status | Meaning | Charged |
|---|---|---|
| queued | Waiting for a GPU. The cost is held from your credit. | Held |
| processing | Running. progress goes from 0 to 1. | Held |
| succeeded | Done. output describes the MP3; download it with /output. | Yes, the held amount |
| failed | Did not finish. error.code and error.message say why. | No, the hold is released |
| canceled | Canceled while queued. | No, the hold is released |
Test-key jobs complete almost at once with a sample MP3 and cost nothing. Every job finishes within 24 hours; one that cannot is failed and not charged.
Polling
Polling is the way to follow a job at launch (webhooks come later). Processing takes a fraction of the audio's length, so poll gently:
- Wait 2 seconds before the first poll, then multiply the wait by 1.5 after each poll, up to 30 seconds.
- Add a little random jitter, so many jobs started together do not poll in lockstep.
- Stop as soon as the status is
succeeded,failedorcanceled. - On
429, waitRetry-Afterseconds. Reads allow 1,200 requests per minute per key, shared by all your pollers. - To follow many jobs, list them (
GET /v1/jobs?status=processing) instead of polling each one.
DELAY=2
while :; do
STATUS=$(curl -s https://api.zilapi.com/v1/jobs/$JOB_ID -H "Authorization: Bearer $ZILAPI_KEY" | jq -r .status)
case "$STATUS" in queued|processing) ;; *) break ;; esac
sleep "$DELAY"
DELAY=$(( DELAY * 3 / 2 > 30 ? 30 : DELAY * 3 / 2 ))
done
echo "$STATUS" # succeeded, failed or canceledCancel
POST /v1/jobs/{id}/cancel cancels a job while it is queued: it becomes canceled, the hold on your credit is released and nothing is charged. Once a job is processing it cannot be canceled (409 job_not_cancelable); it is charged only if it succeeds. Canceling a job that is already canceled returns it unchanged.
curl -s -X POST https://api.zilapi.com/v1/jobs/$JOB_ID/cancel -H "Authorization: Bearer $ZILAPI_KEY" | jq '{status, cost}'Download the result
GET /v1/jobs/{id}/output returns { url, expires_at, filename }: a signed download URL, valid for up to 1 hour. Ask for a fresh one whenever you need it. Before the job succeeds: 409 job_not_succeeded. Results are deleted 7 days after the job succeeds, then 410 output_expired.
curl -s https://api.zilapi.com/v1/jobs/$JOB_ID/output -H "Authorization: Bearer $ZILAPI_KEY" > output.json
curl -o "$(jq -r .filename output.json)" "$(jq -r .url output.json)"List jobs
GET /v1/jobs lists the jobs of the key's mode, newest first. Filter with status and model; page with limit (1 to 100, default 20) and cursor (the previous page's next_cursor; null on the last page).
curl -s "https://api.zilapi.com/v1/jobs?status=failed&limit=50" -H "Authorization: Bearer $ZILAPI_KEY" | jq '(.data[] | {id, error}), .next_cursor'The Job object
{
"id": "job_7Hq2xV9mKp4R",
"object": "job",
"task": "audio.denoise",
"model": "zilapi/nr-4.1",
"status": "succeeded",
"progress": 1.0,
"input": { "file": "file_3Lm8QpZ2rT6w", "duration_seconds": 191.4 },
"output": { "format": "mp3", "bitrate_kbps": 192, "size_bytes": 4608000, "expires_at": "2026-10-13T14:02:31Z" },
"cost": { "estimated_micros": 105600, "charged_micros": 105600, "display": "$0.11" },
"receipt": {
"billable_seconds": 192,
"minimum_applied": false,
"unit_price_micros": 550,
"pricing_version": 1,
"charged_micros": 105600,
"ledger_txn_id": "txn_5Rk2Vb8Lq1Zx"
},
"error": null,
"mode": "live",
"created_at": "2026-10-06T14:02:07Z",
"started_at": "2026-10-06T14:02:09Z",
"finished_at": "2026-10-06T14:02:31Z"
}| Field | Meaning |
|---|---|
| status, progress | Where the job is; progress is 0 to 1. |
| input | The file id and its measured duration_seconds. |
| output | null until the job succeeds; then format, bitrate, size and when it is deleted (expires_at). |
| cost | estimated_micros (held), charged_micros (null until the job finishes or when it was canceled before it started, 0 for a failed job or a test key) and a rounded display string. |
| receipt | How the charge was worked out: see Pricing & billing. |
| error | null, or { code, message } on a failed job. |
| mode | live or test, from the key that created the job. |