Declare the file
POST
/v1 /uploads| Parameter | Type | Required | Description |
|---|---|---|---|
| filename | string | Required | Sets the format. WAV, AIFF, FLAC, MP3, M4A / AAC, OGG / Opus, WMA, CAF, or AMR. |
| size_bytes | int | Optional | Checked before a byte moves. |
| duration_seconds | float | Optional | Checked before a byte moves. Up to 120 min. |
| content_type | string | Optional | Recorded, not trusted. |
| callback_url | string | Optional | Callbacks. |
SIZE=$(wc -c < song.wav)
curl -X POST https://dueta.ai/ v1/ uploads \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ json" \
-d "{\"filename\": \"song.wav\", \"size_bytes\": $SIZE}"
# -> 201 {"id": "upl_9f2c7a41d0e8", "state": "pending",
# "content_url": "/ v1/ uploads/ upl_9f2c7a41d0e8/ content", ...}Send the bytes
PUT
/v1 /uploads /{id} /content# Raw body, not multipart. The response is the upload, now "ready".
curl -X PUT https://dueta.ai/ v1/ uploads/$UPLOAD_ID/content \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ octet-stream" \
--data-binary @song.wav
# -> {"state": "ready", "duration_seconds": 612.4, "sample_rate": 48000, ...}Response
// POST / v1/ uploads -> 201
{
"id": "upl_9f2c7a41d0e8",
"filename": "song.wav",
"content_type": null,
"size_bytes": 91240044,
"bytes_received": 0,
"state": "pending",
"duration_seconds": null,
"sample_rate": null,
"channels": null,
"checksum_sha256": null,
"error": null,
"created_at": "2026-08-28T09:21:44Z",
"expires_at": "2026-08-29T09:21:44Z",
"content_url": "/ v1/ uploads/ upl_9f2c7a41d0e8/ content",
"events_url": "/ v1/ uploads/ upl_9f2c7a41d0e8/ events",
"callback_secret": null
}
// After the last byte: decoded on our disk, every number measured.
{ "state": "ready", "bytes_received": 91240044, "duration_seconds": 612.4,
"sample_rate": 48000, "channels": 2,
"checksum_sha256": "b1946ac9...", "error": null }
// A refusal leaves the upload readable, with the reason.
{ "state": "failed", "bytes_received": 0,
"error": "tiny.wav is only 0.10s long. Audio must be at least 0.5s long." }Resume a transfer
Continue at bytes_received. Other offsets: 409.
# Ask the server what it has, then send from exactly there.
HAVE=$(curl -sS -H "Authorization: Bearer $DUETA_API_KEY" \
https://dueta.ai/ v1/ uploads/$UPLOAD_ID | jq -r .bytes_received)
SIZE=$(wc -c < song.wav)
tail -c +$((HAVE + 1)) song.wav |
curl -X PUT https://dueta.ai/ v1/ uploads/$UPLOAD_ID/content \
-H "Authorization: Bearer $DUETA_API_KEY" \
-H "Content-Type: application/ octet-stream" \
-H "Content-Range: bytes $HAVE-$((SIZE - 1))/ $SIZE" \
--data-binary @-Wait for ready
GET
/v1 /uploads /{id}GET
/v1 /uploads /{id} /events| Field | Type | Description |
|---|---|---|
| expires_at | string | Unused upload expiry (UTC). |
| state | Meaning |
|---|---|
| pending | Declared, no bytes yet. |
| receiving | bytes_received is live. |
| ready | Decoded. Duration, rate, channels, checksum set. |
| failed | error says why and reason_code names it (input_unreadable, input_too_short, input_silent, input_too_long, input_audio_format, input_is_video, input_file_too_large); error_code stays upload_failed. Cut off: send again from byte 0; unreadable or silent audio: create a new upload. |
| expired | Unused 24 hours. Create a new upload. |
# Poll...
curl -H "Authorization: Bearer $DUETA_API_KEY" \
https://dueta.ai/ v1/ uploads/$UPLOAD_ID | jq '{state, bytes_received, error}'
# ...or subscribe. Same body, pushed; ends at ready / failed / expired.
curl -N -H "Authorization: Bearer $DUETA_API_KEY" \
https://dueta.ai/ v1/ uploads/$UPLOAD_ID/eventsCases
- 415
- Video.
- 413
- Over 100 MB.
- 400
- Undecodable, under 0.5 s, silent, bad filename or Content-Range.
- 400 length
- Bytes differ from size_bytes.
- 400 input_too_long
- Longer than
max_upload_seconds, declared or measured. - 409
- Offset is not bytes_received.
- 409 email
- Email unverified. email_unverified.
- 410
- Expired.
- DELETE
DELETE /v1/uploads/{id}:204, or409while a job holds it.- Rate limit
- 20 a minute, apart from job submission.
- Idempotency-Key
- Replays the first answer, with
Idempotent-Replayed: true. 24 h. Different body:422. Still running:409,Retry-After.