API · EN
API · EN

Start

IntroductionQuickstartAuthenticationCore concepts

Guides

Uploading callsGetting resultsWebhooksPagination and search

Details

ErrorsRate limits and billing
API reference
aftertalk.coPricingContactPolski

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

StatusMeaning
PENDINGQueued, not started
PROCESSINGBeing transcribed and analysed
COMPLETEDDone — transcriptId is set
FAILEDProcessing failed; errorMessage explains why
BILLING_FAILEDResults 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.

Uploading calls

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

Webhooks

Be told when a call is ready instead of polling for it.

On this page

Job statusesReading the callAnalysis fields are optional, even on successGetting the audio back