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 suit a long-running integration; polling is simpler for a one-off 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 an overall timeout — processing time scales with audio length.
import os, time, requests
BASE = "https://app.aftertalk.co/api"
headers = {"X-API-Key": os.environ["AFTERTALK_KEY"]}
TERMINAL = {"COMPLETED", "FAILED", "BILLING_FAILED"}
def wait_for_job(project_id, job_id, timeout_s=900):
"""Returns the job once it's terminal. FAILED is terminal too, so check job["status"]."""
deadline = time.time() + timeout_s
while True:
job = requests.get(
f"{BASE}/v1/projects/{project_id}/calls/jobs/{job_id}", headers=headers
).json()
if job["status"] in TERMINAL:
return job
if time.time() > deadline:
raise TimeoutError(f"job {job_id} still {job['status']}")
time.sleep(5)
# The ids the quickstart exports.
job = wait_for_job(os.environ["PROJECT_ID"], os.environ["JOB_ID"])
if job["status"] == "FAILED":
raise RuntimeError(f"Processing failed: {job.get('errorMessage')}")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 as speaker turns (turns) plus the analysis your project produces — summary,
sentiment, category, any template analysis, and an action items count. The action items themselves
come from the action-items endpoints. 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?.summary?.executiveSummary, not call.summary.summary.executiveSummary — or a single unusual call will
break your integration.
Getting the audio back
GET /calls/{id}/audio returns metadata and a link to the stored recording, for a player or an
archive copy.