API · EN
API · EN

Start

IntroductionQuickstartAuthenticationCore concepts

Guides

Uploading callsGetting resultsWebhooksPagination and search

Details

ErrorsRate limits and billing
API reference
aftertalk.coPricingContactPolski

Uploading calls

Formats, speaker roles, size limits, and duplicate handling.

Upload a call by sending multipart/form-data to the project's upload endpoint:

curl -X POST "$BASE/v1/projects/$PROJECT_ID/calls/upload" \
  -H "X-API-Key: $AFTERTALK_KEY" \
  -F "file=@call.wav;type=audio/wav"

The response is 202 Accepted with a job id — processing happens in the background. See getting results.

Supported audio

FormatsMP3 (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 size10 MB per file as multipart/form-data; 50 MB with a direct upload

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. curl doesn't know audio types and sends application/octet-stream unless you add ;type= to the file, as in the examples here.

Files over 10 MB: direct upload

Larger files go straight to storage instead of through the API, in three steps. Ask for an upload URL with the file's name, type and exact size:

SIZE=$(wc -c < call.wav | tr -d ' ')
UPLOAD=$(curl -s -X POST "$BASE/v1/projects/$PROJECT_ID/calls/upload-url" \
  -H "X-API-Key: $AFTERTALK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename":"call.wav","contentType":"audio/wav","sizeBytes":'"$SIZE"'}')
export UPLOAD_ID=$(echo "$UPLOAD" | jq -r '.uploadId')
export UPLOAD_URL=$(echo "$UPLOAD" | jq -r '.uploadUrl')

The format, size, project access and allowance are checked here, so a file that couldn't be processed is refused before you send it. The response holds an uploadId and an uploadUrl that stays valid for 10 minutes. PUT the file to that URL, with no API key, and with exactly the Content-Type and size you declared; both are part of the URL's signature, so anything else is rejected with 403:

curl -X PUT "$UPLOAD_URL" -H "Content-Type: audio/wav" --data-binary "@call.wav"

Then queue it. language, channelRoles (the JSON spelling of channel_roles) and force work as they do on the multipart upload:

curl -X POST "$BASE/v1/projects/$PROJECT_ID/calls/upload-complete" \
  -H "X-API-Key: $AFTERTALK_KEY" \
  -H "Content-Type: application/json" \
  -d '{"uploadId":"'"$UPLOAD_ID"'","filename":"call.wav","contentType":"audio/wav","language":"pl"}'

This returns 202 Accepted, and the job id is the uploadId. Completing before the PUT finished returns 404, and completing the same upload twice returns 409. A duplicate also returns 409, but the file is kept: send the same request with "force": true to process it anyway without uploading it again.

Transcription language

Calls are transcribed in Polish or English. Pass language as pl or en to say which:

curl -X POST "$BASE/v1/projects/$PROJECT_ID/calls/upload" \
  -H "X-API-Key: $AFTERTALK_KEY" \
  -F "file=@call.wav;type=audio/wav" \
  -F "language=en"

Omitting the parameter falls back to the project's defaultLanguage, which is pl unless you set it otherwise (PUT /v1/projects/{projectId} with {"defaultLanguage": "en"}, or defaultLanguage on create). Pick the language deliberately for English audio — the wrong one degrades the transcript, and with it every analysis derived from the transcript. A language other than pl or en is rejected with 400; nothing is stored or queued.

An explicit language still applies per upload, so a project can hold calls in both — the project default only applies to uploads that don't specify a language. There is no auto-detection: an unset language means the project default rather than "work it out from the audio".

Mapping speakers to roles

For stereo recordings where each channel is one party, pass channel_roles as a JSON object mapping channel index (0 = left, 1 = right) to a role. The transcript then attributes each turn 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;type=audio/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.

Allowance exhausted

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.

Core concepts

Projects, calls, categories, templates, presets, and action items.

Getting results

Track a job to completion and read the analysis.

On this page

Supported audioFiles over 10 MB: direct uploadTranscription languageMapping speakers to rolesDuplicate uploadsAllowance exhausted