Skip to content
DUETA
Docs

Track a job

Follow a job to a terminal state by polling, streaming, or callbacks, and cancel it if needed.

On this page
You need
  • A job id from Run a separation.
  • For callbacks: an HTTPS endpoint of yours, and the callback_secret from the creation response.

Poll the job

GET/v1/jobs/{id}

Every second or two is plenty. Stop on succeeded, failed or canceled. Render the indicator from steps[], which is always complete and in order; never from a local copy of the pipeline.

json
{
  "status": "running",
  "stage": "separation",
  "progress": 0.62,
  "eta_seconds": 34,
  "queue_position": null,
  "worker_alive": true,
  "heartbeat_age_seconds": 0.8,
  "steps": [
    { "name": "vocal_separation", "state": "done",    "progress": 1.0 },
    { "name": "separation",       "state": "running", "progress": 0.42 },
    { "name": "enhancement",      "state": "pending", "progress": 0.0 },
    { "name": "super_resolution", "state": "skipped", "progress": 0.0 },
    { "name": "si_sdr",           "state": "pending", "progress": 0.0 }
  ]
}
statusstageMeaning
queuedqueuedWaiting. queue_position counts the jobs ahead; 0 is next.
running vocal_separation separation enhancement super_resolution si_sdrThe Duplex phases in order. A Dominant job reports one step, run.
succeededdoneresult is set, billed_usd equals the quote.
failedfailederror says why. Never charged.
canceledcanceledStopped at your request. Never charged.
FieldTypeMeaning
progress0..1Across the whole job. Only moves forward.
steps[]object[]name, state (pending, running, done, skipped) and that step's own progress.
queue_positionint | nullJobs ahead while queued; null once running or finished.
eta_secondsfloat | nullProcessing time left, excluding queue wait; null while anything is ahead.
worker_alivebool | nullWhether a processing worker has reported recently. Null on finished jobs or when it cannot be asked.
heartbeat_age_secondsfloat | nullSeconds since the worker's last report: this job's own tick while running, the worker's stamp while queued.
bash
while :; do
  JSON=$(curl -sS -H "Authorization: Bearer $DUETA_API_KEY" https://dueta.ai/v1/jobs/$JOB_ID)
  echo "$JSON" | jq -r '[.status, .stage, (.progress * 100 | floor | tostring) + "%"] | join("  ")'
  case $(echo "$JSON" | jq -r .status) in
    succeeded) break ;;
    failed|canceled) echo "$JSON" | jq -r .error; exit 1 ;;
  esac
  sleep 2
done

Subscribe to events

GET/v1/jobs/{id}/events

The same resource, pushed. Each event: job frame carries exactly the body the GET would return at that moment, so one render path serves both. Comment frames keep the connection warm; an event: done frame ends the stream once the job is terminal. Uploads stream the same way at /v1/uploads/{id}/events.

bash
# -N so curl prints frames as they arrive. Each data: line is the full job.
curl -N -H "Authorization: Bearer $DUETA_API_KEY" https://dueta.ai/v1/jobs/$JOB_ID/events

# event: job
# data: {"id":"0f9c1a7e-...","status":"running","stage":"separation","progress":0.62,...}
#
# : keepalive
#
# event: done
# data: {"id":"0f9c1a7e-...","status":"succeeded"}

Receive callbacks

Send callback_url when you create a job or an upload, and keep the callback_secret from that one response. Each delivery is a POST whose data is the whole resource, signed with HMAC-SHA256 over {timestamp}.{raw body}. Verify over the raw bytes, refuse a timestamp older than 300 s, and answer any 2xx.

FieldTypeMeaning
X-Dueta-Eventheaderjob.running, job.succeeded, job.failed, job.canceled; upload.receiving, upload.ready, upload.failed.
X-Dueta-TimestampheaderUnix seconds when it was sent.
X-Dueta-Signatureheaderv1= plus the hex HMAC.
event, type, databodyThe event name, job or upload, and the resource.
bash
# Register on the job (or on an upload, same field). The secret is in THIS
# response and never again.
curl -X POST https://dueta.ai/v1/jobs/separation \
  -H "Authorization: Bearer $DUETA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"inputs": ["upl_9f2c7a41d0e8"],
       "callback_url": "https://yours.example.com/hooks/dueta"}'
# -> {"id": "...", "callback_secret": "whsec_...store-this-now", ...}

# One delivery, as your server sees it:
# POST /hooks/dueta
# X-Dueta-Event: job.succeeded
# X-Dueta-Timestamp: 1787881026
# X-Dueta-Signature: v1=<hex HMAC-SHA256 of "<timestamp>.<raw body>">
# {"event": "job.succeeded", "sent_at": 1787881026, "type": "job", "data": {...the job...}}

Cancel

POST/v1/jobs/{id}/cancel

A queued job is canceled at once and its hold released. A running job is canceled cooperatively: the worker stops at its next progress tick, so the response still reads running until it does. A canceled job is never charged; if the worker finishes in the gap, the finish wins.

bash
curl -X POST https://dueta.ai/v1/jobs/$JOB_ID/cancel \
  -H "Authorization: Bearer $DUETA_API_KEY"
# queued  -> {"status": "canceled", ...}   hold released now
# running -> {"status": "running", ...}    canceled at the worker's next tick

Cases

409
Cancel on a job that is already succeeded, failed, or canceled. Treat it as done.
worker_alive: false
No worker is reporting. A queued job is failed with "No processing worker was available" after 10 minutes of this and its hold released.
Heartbeat
A running job whose heartbeat goes quiet for 120 s is failed and its hold released. Watch heartbeat_age_seconds rather than assuming a long step.
Progress
The bar never moves backward: a lost live update is floored at the last stored checkpoint.
Callbacks
Best effort and unordered: a hint that something changed, not a ledger. Re-read the resource when it matters. Failed deliveries are retried a few times.
EventSource
Cannot send an Authorization header, so an API-key client reads the stream with fetch; a browser holding the session cookie can use EventSource.
List
GET /v1/jobs?limit=20&offset=0 lists your jobs newest first, without worker liveness. All job routes.