Get a quote
POST
/v1 /jobs /quote| Parameter | Type | Required | Description |
|---|---|---|---|
| type | string | Required | separation, multi_mic_separation, target_, music_separation or noise_removal. |
| inputs | string[] | Required | Upload ids, in order. |
| range | object | Optional | {start_seconds, end_seconds}: price one stretch, as the submission would. |
| Field | Type | Description |
|---|---|---|
| balance_usd | float | null | null for an organization key without the billing permission. sufficient_credit still answers. |
curl -X POST https://dueta.ai/ v1/ jobs/ quote \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d '{"type": "separation", "inputs": ["upl_9f2c7a41d0e8"]}'Response
{
"type": "separation",
"inputs": ["upl_9f2c7a41d0e8"],
"billable_seconds": 612.4,
"total_duration_seconds": 612.4,
"estimated_cost_usd": 5.1033,
"balance_usd": 20.0,
"sufficient_credit": true,
"can_submit": true,
"reasons": []
}
// Not submittable yet: every refusal comes back as a sentence, not an error.
{ "sufficient_credit": false, "can_submit": false,
"reasons": ["These uploads have not finished: upl_4a81be03c5f7",
"This job costs more than your balance. Add credit in the console."] }Run part of a recording
| Rule | Value |
|---|---|
start_seconds, end_seconds | Number · required · seconds from the start |
| Offered on | A recording over 30s |
| Length | 30s or more · end_seconds minus start_seconds |
| Bounds | 0 ≤ start_seconds < end_seconds ≤ recording length |
| Whole recording | Same as no range |
| Billed | The range length |
| Multi Mic Separation | One range cuts every microphone |
| Target Speaker Extraction | Cuts the recording, never the voice sample |
Music Source Separation, source_url | With its probe_id, where GET /v1/limits has source_url.range true · checked against the probed length |
| Multipart form | range_start_seconds and range_end_seconds · both or neither |
| 422 | input_range_invalid |
| 503 | input_range_busy · retry after the Retry-After seconds |
| 409 | submission_collided |
# Run seconds 62.5 to 140 only, then price the same stretch.
curl -X POST https://dueta.ai/ v1/ jobs/ noise-remover \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d '{"inputs": ["upl_9f2c7a41d0e8"],
"range": {"start_seconds": 62.5, "end_seconds": 140}}'
curl -X POST https://dueta.ai/ v1/ jobs/ quote \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d '{"type": "noise_removal", "inputs": ["upl_9f2c7a41d0e8"],
"range": {"start_seconds": 62.5, "end_seconds": 140}}'Run Music Source Separation
Split music into an audio file for each track.
POST
/v1 /jobs /music-separation| Parameter | Type | Required | Description |
|---|---|---|---|
| inputs | string[1] | Optional | The song. One of inputs, source_url. |
| source_url | string | Optional | A YouTube link; the audio is fetched into an upload of its own. youtube.com/watch?v=, youtu.be/, music.youtube.com, m.youtube.com, shorts/. |
| range | object | Optional | {start_seconds, end_seconds}. On a song over 30 s, an upload, or a source_url with its probe_id where source_url.range is true: the stretch to run and bill, 30 s or more. Default: the whole song. input_range_invalid otherwise. |
| callback_url | string | Optional | Callbacks. |
| Limit | Value |
|---|---|
| Song | Up to 15 min · a file, or a YouTube link |
| Quote, link | 15 min held · warnings[].code source_length_unknown · charged for the real length |
| Hold, link | At least 1 min ($1) without probe_id · probe first for the exact price |
| Input | Ideal 44.1 kHz or higher · up to 192 kHz · stereo kept |
| Output | 14 stems · WAV · 44.1 kHz · stereo · same length |
| Stems | kick · snare_clap · hihat · ride · crash · perc · sub_bass · mid_bass · mid_lead · pad · pluck · lead_vocal · backing_vocal · fx_other |
| Price | $1/min of song · a 3-min song is $3 |
curl -X POST https://dueta.ai/ v1/ jobs/ music-separation \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d '{"inputs": ["upl_9f2c7a41d0e8"]}'
# -> 201, the job. result.stems lists the 14 stems once it succeeds.
# From a YouTube link instead of an upload:
# -d '{"source_url": "https://www.youtube.com/ watch?v=dQw4w9WgXcQ"}'Run Duplex Conversation Separation
Split a two-person conversation into a WAV file for each speaker.
POST
/v1 /jobs /separation| Parameter | Type | Required | Description |
|---|---|---|---|
| inputs | string[1] | Required | Exactly one upload id, up to 30 min. |
| pipeline | object | Optional | A missing key keeps its default. |
| range | object | Optional | {start_seconds, end_seconds}. On a recording over 30 s: the stretch to run and bill, 30 s or more; default: the whole recording. input_range_invalid otherwise. |
| callback_url | string | Optional | Callbacks. |
| pipeline key | Default | Effect |
|---|---|---|
| vocal_separation | true | Separates only the voices from the uploaded audio and removes background music and noise. Turn it off when the recording is already dry speech: a studio double-ender, a headset call, a meeting-room capture with nothing playing. |
| enhancement | true | Removes the noise left on each track so the speech is clearer. Turn it off on recordings that are already clean, where there is nothing to remove and denoising can only soften the voice. |
| super_resolution | true | Turns the 16 kHz result into 48 kHz audio for better sound quality. Turn it off when your pipeline reads 16 kHz audio and would throw the extra bandwidth away, as speech recognition and telephony do. |
| scoring | true | Our own model estimates each track's SI-SDR from the separated audio alone and adds it to the result. Turn it off when you never read the number, such as an automated pipeline that keeps whatever comes back. |
| Audio | Value |
|---|---|
| Input | Ideal 44.1 kHz or higher · up to 192 kHz · mono; stereo is averaged |
| Output | WAV · 48 kHz with super_resolution · 16 kHz without |
| Price | $0.50/min of recording |
curl -X POST https://dueta.ai/ v1/ jobs/ separation \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d '{"inputs": ["upl_9f2c7a41d0e8"],
"pipeline": {"super_resolution": false}}'
# -> 201, the job. Omit "pipeline" to run every step.Run Multi Mic Separation
Remove other voices so each mic keeps only its speaker.
POST
/v1 /jobs /multi-mic-separationJobs since 2026-10-16 report type multi_mic_separation; /v1/jobs/dominant-separation and dominant_separation stay accepted forever.
| Parameter | Type | Required | Description |
|---|---|---|---|
| inputs | string[2..10] | Required | One per microphone, in alignment order. |
| stages | object | Optional | A missing key keeps its default. |
| range | object | Optional | {start_seconds, end_seconds}. On microphones over 30 s: one stretch cut from every microphone, inside the shortest, 30 s or more; default: the whole take. input_range_invalid otherwise. |
| callback_url | string | Optional | Callbacks. |
| stages key | Default | Effect |
|---|---|---|
| sync | true | Aligns the recordings from every microphone to the same moment. Turn it off when every recording came off one device that started them together, and is already sample-aligned. |
| enhance | true | Removes the noise left on each track so the speech is clearer. Turn it off on recordings that are already clean, where there is nothing to remove and denoising can only soften the voice. |
| bwe | true | Turns the 16 kHz result into 48 kHz audio for better sound quality. Turn it off when your pipeline reads 16 kHz audio and would throw the extra bandwidth away, as speech recognition and telephony do. |
| Audio | Value |
|---|---|
| Input | Ideal 16 kHz or higher · up to 192 kHz · one mono recording per microphone |
| Output | WAV · 48 kHz with bwe · 16 kHz without · result.sample_rate |
| Price | $0.50/min of each microphone, summed |
# 2-10 upload ids, one per microphone. The FIRST is the timeline the
# others are aligned against. A step you do not name keeps its default.
curl -X POST https://dueta.ai/ v1/ jobs/ multi-mic-separation \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d '{"inputs": ["upl_9f2c7a41d0e8", "upl_4a81be03c5f7"],
"stages": {"bwe": false}}'Run Target Speaker Extraction
Extract only the person you want from a short voice sample.
POST
/v1 /jobs /target-speaker-extraction| Parameter | Type | Required | Description |
|---|---|---|---|
| inputs | string[2] | Required | The recording, then the target voice. |
| range | object | Optional | {start_seconds, end_seconds}. On a recording over 30 s: the stretch of the recording to run and bill, 30 s or more; the voice sample is never cut, and the default is the whole recording. input_range_invalid otherwise. |
| callback_url | string | Optional | Callbacks. |
| Limit | Value |
|---|---|
| Recording | Up to 120 min |
| Target voice | 3 s of speech, 300 s max · 10 s or more recommended |
| Input | Ideal 16 kHz or higher · up to 192 kHz · mono; stereo is averaged |
| Output | target · WAV · 16 kHz · same length · no bandwidth extension |
| Price | $0.50/min of recording · target voice free |
AUTH="Authorization: Bearer $DUETA_API_KEY"
upload() { # declare, send the bytes, print the upload id; fails unless they landed
ID=$(curl -sS -X POST -H "$AUTH" -H "Content-Type: application/ json" \
-d "{\"filename\": \"$1\", \"size_bytes\": $(wc -c < "$1")}" \
https://dueta.ai/ v1/ uploads | jq -er .id) || { echo "upload refused: $1" >&2; return 1; }
curl -fsS -X PUT -H "$AUTH" -H "Content-Type: application/ octet-stream" \
--data-binary @"$1" https://dueta.ai/ v1/ uploads/$ID/content |
jq -e '.state == "ready"' > / dev/ null || { echo "upload failed: $1" >&2; return 1; }
echo "$ID"
}
MIX=$(upload conversation.wav) || exit 1 # inputs[0]: the recording
VOICE=$(upload voice.wav) || exit 1 # inputs[1]: the target voice, 3-300 s
JOB=$(curl -sS -X POST -H "$AUTH" -H "Content-Type: application/ json" \
-d "{\"inputs\": [\"$MIX\", \"$VOICE\"]}" \
https://dueta.ai/ v1/ jobs/ target-speaker-extraction)
JOB_ID=$(echo "$JOB" | jq -er .id) || { echo "$JOB"; exit 1; } # an error body (credit, limits) has no id
while :; do
JOB=$(curl -sS -H "$AUTH" https://dueta.ai/ v1/ jobs/$JOB_ID)
case "$(echo "$JOB" | jq -r .status)" in
succeeded) break ;;
queued|running) sleep 5 ;;
failed|canceled) echo "$JOB" | jq -r .error_code; exit 1 ;;
*) echo "$JOB"; exit 1 ;; # not a job at all: an error body
esac
done
curl -fsSL -H "$AUTH" -o target.wav https://dueta.ai/ v1/ jobs/$JOB_ID/stems/ targetRun Noise Reduction
Remove background noise and keep the voice clear.
POST
/v1 /jobs /noise-remover| Parameter | Type | Required | Description |
|---|---|---|---|
| inputs | string[1] | Required | The recording. |
| range | object | Optional | {start_seconds, end_seconds}. On a recording over 30 s: the stretch to run and bill, 30 s or more; default: the whole recording. input_range_invalid otherwise. |
| callback_url | string | Optional | Callbacks. |
| Limit | Value |
|---|---|
| Recording | Up to 120 min |
| Input | up to 192 kHz · mono or stereo; stereo is averaged |
| Output | clean · WAV · mono · same length · the input's rate, 16 kHz at least |
| Rate | Over 16 kHz: the band above 8 kHz is extended by a model · result. · result.sample_rate is the delivered rate |
| Level | The input's, within 1 dB · peak under −0.1 dBFS |
| Output level | Below the audibility threshold: failed, silent_output, not charged |
| Price | $0.50/min of recording |
curl -X POST https://dueta.ai/ v1/ jobs/ noise-remover \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d '{"inputs": ["upl_9f2c7a41d0e8"]}'
# -> 201, the job. result.stems is [{"name": "clean"}] once it succeeds;
# GET / v1/ jobs/ $JOB_ID/ stems/ clean is the WAV.Read the job back
GET
/v1 /jobs /{id}| Field | Type | Description |
|---|---|---|
| id | string | The job id. |
| status | string | queued at creation. Job status. |
| inputs[] | object[] | One record per recording. role: mixture or enrollment on Target Speaker Extraction, else null. |
| pipeline | object | Duplex Conversation Separation only. Empty on a Multi Mic Separation job. |
| result.processing | object | What ran. Prefer it over stages. |
| stages | object | Multi Mic Separation only. What was asked for. |
| estimated_cost_seconds | float | Input seconds billed: every input summed, Target Speaker Extraction the recording only. Each model's own rate per input minute: rate_usd_per_minute in GET /v1/limits; rates may differ by model. |
| estimated_cost_usd | float | Held now, charged on success. |
| paid_by | object | {"kind": "personal"}, or organization with slug, name. |
| callback_secret | string | Creation response only. |
curl -H "Authorization: Bearer $DUETA_API_KEY" https://dueta.ai/ v1/ jobs/$JOB_ID |
jq '{status, stage, progress, queue_position, estimated_cost_usd, pipeline}'Response
{
"id": "0f9c1a7e-4b21-4d5a-9d0e-2c8b6a1f3e77",
"type": "separation",
"status": "queued",
"stage": "queued",
"progress": 0,
"eta_seconds": null,
"eta_range_seconds": null,
"eta_updated_at": null,
"delay_reason": null,
"queue_position": 2,
"queue_wait_seconds": 108.2,
"worker_alive": true,
"heartbeat_age_seconds": 3.2,
"gpu_degraded": false,
"pipeline": { "vocal_separation": true, "enhancement": true,
"super_resolution": false, "scoring": true },
"inputs": [
{ "index": 0, "upload_id": "upl_9f2c7a41d0e8", "filename": "conversation.wav",
"duration_seconds": 612.4, "stem": "conversation-00", "sample_rate": 48000,
"channels": 2, "state": "ready", "checksum_sha256": "b1946ac9..." }
],
"steps": [
{ "name": "vocal_separation", "state": "pending", "progress": 0 },
{ "name": "separation", "state": "pending", "progress": 0 },
{ "name": "enhancement", "state": "pending", "progress": 0 },
{ "name": "super_resolution", "state": "skipped", "progress": 0 },
{ "name": "si_sdr", "state": "pending", "progress": 0 }
],
"estimated_cost_seconds": 612.4,
"estimated_cost_usd": 5.1033,
"billed_seconds": null,
"billed_usd": null,
"callback_secret": null,
"error": null,
"result": null,
"created_at": "2026-08-28T09:21:44Z",
"started_at": null,
"finished_at": null
}Cases
- 402
- Insufficient credit. Nothing charged.
- 404
- Unknown input id.
- 409
- Input not ready, or email unverified.
- 400 count
- 1 file for
separation,music-separationandnoise-remover, 2–10 formulti-mic-separation, 2 fortarget-speaker-extraction. - 400 duration
- Over 120 min, summed.
- 400 length
- Duplex Conversation Separation over 30 min, Music Source Separation over 15 min.
- 400 take
- Longest over 2× the shortest.
- 400 body
pipelinevalues not booleans.- 422 step
- Unknown
stageskey, orseparateoff. - 422 field
stageson Duplex Conversation Separation.- 422 enrollment
enrollment_is_mixture,enrollment_too_short,enrollment_too_long. Nothing held.- 415
- Video part.
- 429
- 1 job (4 paid), 20 a minute, 20 unbilled/hour.
- 503
- Queue unreachable. Back off.
- Idempotency-Key
- Replays the first answer, with
Idempotent-Replayed: true. Also onPOST /v1/uploads. 24 h; 255 characters. - 422 key
- Different body; JSON matches by content, not key order.
- 409 key
- Still running. Wait
Retry-After. - Retry
- Same
inputs, order and steps while unfinished: that job,201. - Retry scope
- Not multipart or
url. - Retry callback
- Kept if one exists.
- Re-run
- Finished: a new, charged job.