Skip to content
Log inGet an API key
Concepts

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.

Create a denoise job
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.

Job statuses
StatusMeaningCharged
queuedWaiting for a GPU. The cost is held from your credit.Held
processingRunning. progress goes from 0 to 1.Held
succeededDone. output describes the MP3; download it with /output.Yes, the held amount
failedDid not finish. error.code and error.message say why.No, the hold is released
canceledCanceled 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, failed or canceled.
  • On 429, wait Retry-After seconds. 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.
Poll with backoff until the job finishes
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 canceled

Cancel

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.

Cancel a queued job
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.

Download the MP3
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).

List failed jobs, 50 at a time
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

200 OK · GET /v1/jobs/job_7Hq2xV9mKp4R
{
  "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"
}
Job fields
FieldMeaning
status, progressWhere the job is; progress is 0 to 1.
inputThe file id and its measured duration_seconds.
outputnull until the job succeeds; then format, bitrate, size and when it is deleted (expires_at).
costestimated_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.
receiptHow the charge was worked out: see Pricing & billing.
errornull, or { code, message } on a failed job.
modelive or test, from the key that created the job.