Getting results
Track a job to completion and read the analysis.
Uploading returns a job id. Either poll that job until it reaches a terminal state, or register a webhook and be told when it's done — webhooks are the better default for anything long-running, polling is simpler for a script.
curl "$BASE/v1/projects/$PROJECT_ID/calls/jobs/$JOB_ID" \
-H "X-API-Key: $AFTERTALK_KEY"Job statuses
| Status | Meaning |
|---|---|
PENDING | Queued, not started |
PROCESSING | Being transcribed and analysed |
COMPLETED | Done — transcriptId is set |
FAILED | Processing failed; errorMessage explains why |
BILLING_FAILED | Results are saved and readable, but usage couldn't be recorded |
PENDING and PROCESSING are the only non-terminal states. Stop polling on anything else.
BILLING_FAILED is not a data failure: the transcript and analysis are saved and transcriptId is set, so treat it
like COMPLETED for reading results. It signals a billing problem to sort out in the app, not a reason to re-upload.
A reasonable poll is every 5 seconds with a ceiling — processing time scales with audio length.
import time
TERMINAL = {"COMPLETED", "FAILED", "BILLING_FAILED"}
def wait_for_job(project_id, job_id, timeout_s=900):
deadline = time.time() + timeout_s
while time.time() < deadline:
job = requests.get(
f"{BASE}/v1/projects/{project_id}/calls/jobs/{job_id}", headers=headers
).json()
if job["status"] in TERMINAL:
return job
time.sleep(5)
raise TimeoutError(f"job {job_id} still {job['status']}")Reading the call
Once transcriptId is set:
curl "$BASE/v1/projects/$PROJECT_ID/calls/$TRANSCRIPT_ID" \
-H "X-API-Key: $AFTERTALK_KEY"You get the transcript segments plus the analysis your project produces — summary, sentiment, category, action items, and any template analysis. The reference has the full response schema.
Analysis fields are optional, even on success
Transcription is required; every analysis step after it is best-effort. If classification or summarisation fails for one call, the job still completes with the transcript saved and that particular field absent.
Never assume an analysis field exists just because the job is COMPLETED. Read defensively — call.summary?.text,
not call.summary.text — or a single unusual call will break your integration.
Getting the audio back
GET /calls/{id}/audio returns metadata and a link for the stored recording, for a player or an
archive copy.