Recipes / Call centreSign inGet an API key

Call centre

Transcribe stereo calls one party per channel, then measure talk share, talk-overs and dead air.

Why separate channels

Most call recorders put the agent on one channel and the customer on the other. With channel_mode: "separate" each channel is transcribed on its own, so every segment is attributed exactly — no guessing who spoke — and nothing is lost when both talk at once.

python call_centre.py call-0192.wav ta-IN --agent-channel 0 > call-0192.json

At volume

  • Send each call with its own Idempotency-Key (the call ID works well), so a retried batch never transcribes a call twice.
  • Register a webhook and process calls as they finish instead of waiting on each one.
  • Separate channels are billed per channel transcribed; see Limits and billing.

The script

call_centre.py
"""Call centre: transcribe a stereo call one channel at a time and compute simple call metrics.

    python call_centre.py call.wav ta-IN --agent-channel 0 > call.json

Needs TWENTYSIX_API_KEY and TWENTYSIX_BASE_URL. Separate channels are billed per channel transcribed.
"""
import argparse
import json

from twentysix import Client


def analyse(result, agent_channel=0):
    role = {agent_channel: "agent", 1 - agent_channel: "customer"}
    segs = sorted((s for s in result["segments"] if "start" in s), key=lambda s: s["start"])
    talk = {"agent": 0.0, "customer": 0.0}
    for s in segs:
        talk[role[s.get("channel", 0)]] += s["end"] - s["start"]
    # Talk-over: a segment starting while the other party's segment is still running.
    over, last_end = 0, {"agent": 0.0, "customer": 0.0}
    for s in segs:
        me = role[s.get("channel", 0)]
        other = "customer" if me == "agent" else "agent"
        if s["start"] < last_end[other]:
            over += 1
        last_end[me] = max(last_end[me], s["end"])
    # Longest silence: gaps where neither party speaks.
    silence, end = 0.0, 0.0
    for s in segs:
        silence = max(silence, s["start"] - end)
        end = max(end, s["end"])
    total = talk["agent"] + talk["customer"] or 1.0
    return {
        "duration_seconds": result["duration_seconds"],
        "agent_talk_share": round(talk["agent"] / total, 3),
        "customer_talk_share": round(talk["customer"] / total, 3),
        "talk_overs": over,
        "longest_silence_seconds": round(silence, 1),
        "transcript": [{"who": role[s.get("channel", 0)], "start": s["start"], "end": s["end"], "text": s["text"]} for s in segs],
    }


if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("path")
    ap.add_argument("language", nargs="?", default="hi-IN")
    ap.add_argument("--agent-channel", type=int, choices=(0, 1), default=0)
    a = ap.parse_args()
    with Client() as client:
        result = client.transcribe_file(a.path, a.language, mode="codemix", channel_mode="separate")
    print(json.dumps(analyse(result, a.agent_channel), ensure_ascii=False, indent=2))
Scriba is a product of 26AI Private Limited, Pune, India.© 2026 26AI Private Limited