Uploading calls
Formats, speaker roles, size limits, and duplicate handling.
Uploads are multipart/form-data to a project:
curl -X POST "$BASE/v1/projects/$PROJECT_ID/calls/upload" \
-H "X-API-Key: $AFTERTALK_KEY" \
-F "file=@call.wav"The response is 202 Accepted with a job id — processing happens in the background. See
getting results.
Supported audio
| Formats | MP3 (audio/mpeg), WAV (audio/wav, audio/x-wav), MP4/M4A (audio/mp4, audio/x-m4a), AAC (audio/aac), OGG (audio/ogg), WebM (audio/webm) |
| Max size | 10 MB per file |
An unsupported format or an oversized file returns 400. Send the right Content-Type for the
part — that's what the check uses, not the file extension.
Mapping speakers to roles
For stereo recordings where each channel is one party, pass channel_roles as a JSON object mapping
channel index to a role. The transcript then attributes each segment to a named role instead of
"Speaker 1":
curl -X POST "$BASE/v1/projects/$PROJECT_ID/calls/upload" \
-H "X-API-Key: $AFTERTALK_KEY" \
-F "file=@call.wav" \
-F 'channel_roles={"0":"agent","1":"customer"}'Malformed JSON here is ignored rather than rejected — the call still processes, just without role mapping. Double-check the value if roles are missing from the result.
To reuse the same mapping across a project, configure a speaker preset and skip the parameter.
Duplicate uploads
Uploads are de-duplicated per project by the audio's content hash. Re-sending bytes that already
exist in the project returns 409 Conflict with the id of the original job:
{
"existingJobId": "8f14e45f-ea0f-4e21-9c1a-2b3c4d5e6f70",
"existingFilename": "call.wav",
"code": "DUPLICATE_FILE"
}This makes retries safe: if a request times out and you send it again, you get a 409 pointing at
the job that already exists instead of a second charge. Treat 409 as success and continue with
existingJobId:
const res = await fetch(uploadUrl, { method: 'POST', headers, body: form });
if (res.status === 409) {
const { existingJobId } = await res.json();
return existingJobId; // already uploaded — carry on
}
const { jobId } = await res.json();
return jobId;A previously failed upload doesn't block a retry — you can re-send the same audio after a
failure. To upload identical audio deliberately, add force=true, which skips the duplicate check
and creates a separate call.
Insufficient balance
If the organization has no audio allowance left, the upload is rejected with 402 Payment Required
before the file is stored. See rate limits and billing.