On this page
- A job id from Run a separation.
- For callbacks: an HTTPS endpoint of yours, and the
callback_secretfrom the creation response.
Poll the job
/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.
{
"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 }
]
}| status | stage | Meaning |
|---|---|---|
| queued | queued | Waiting. queue_position counts the jobs ahead; 0 is next. |
| running | vocal_separation separation enhancement super_resolution si_sdr | The Duplex phases in order. A Dominant job reports one step, run. |
| succeeded | done | result is set, billed_usd equals the quote. |
| failed | failed | error says why. Never charged. |
| canceled | canceled | Stopped at your request. Never charged. |
| Field | Type | Meaning |
|---|---|---|
| progress | 0..1 | Across the whole job. Only moves forward. |
| steps[] | object[] | name, state (pending, running, done, skipped) and that step's own progress. |
| queue_position | int | null | Jobs ahead while queued; null once running or finished. |
| eta_seconds | float | null | Processing time left, excluding queue wait; null while anything is ahead. |
| worker_alive | bool | null | Whether a processing worker has reported recently. Null on finished jobs or when it cannot be asked. |
| heartbeat_age_seconds | float | null | Seconds since the worker's last report: this job's own tick while running, the worker's stamp while queued. |
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
doneSubscribe to events
/v1/jobs/{id}/eventsThe 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.
# -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.
| Field | Type | Meaning |
|---|---|---|
| X-Dueta-Event | header | job.running, job.succeeded, job.failed, job.canceled; upload.receiving, upload.ready, upload.failed. |
| X-Dueta-Timestamp | header | Unix seconds when it was sent. |
| X-Dueta-Signature | header | v1= plus the hex HMAC. |
| event, type, data | body | The event name, job or upload, and the resource. |
# 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
/v1/jobs/{id}/cancelA 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.
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 tickCases
- 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=0lists your jobs newest first, without worker liveness. All job routes.