Most earnings call transcript sources hand you one long block of text. That's fine for reading, but as soon as you want to measure something (how long the CEO talks, what analysts asked, how the answers changed from one quarter to the next) you need every turn on its own, with who said it and in what role.
This is the short script I use to pull a call speaker by speaker from the Equibles earnings call transcript API. A free key is enough: the free plan allows 100 requests a day across the MCP server and the REST API, with no credit card, and a call of up to 200 turns comes back in one request. I'll use NVIDIA's May 2026 call as the example.
Setup
pip install requests
export EQUIBLES_API_KEY=eq_your_api_key
import os
from collections import Counter
import requests
API = "https://api.equibles.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['EQUIBLES_API_KEY']}"}
def get(path, **params):
r = requests.get(f"{API}{path}", headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
return r.json()
1. Find the call, and watch the fiscal year
Calls are keyed by the company's own fiscal year and quarter, not the calendar. NVIDIA's fiscal year ends in late January, so its May 2026 call is FY2027 Q1, not 2026 Q2. Listing the company's earnings calls first saves the guessing:
events = get("/stocks/NVDA/investor-events", eventType="EarningsCall", limit=4)
for e in events["data"]:
print(e["callDate"][:10], f"FY{e['fiscalYear']} Q{e['fiscalQuarter']}", e["hasTranscript"])
2026-11-17 FY2027 Q3 False
2026-08-26 FY2027 Q2 True
2026-05-20 FY2027 Q1 True
2026-02-25 FY2026 Q4 True
The top row is the November call, which hasn't happened yet, so hasTranscript is False. Check that flag before you ask for a transcript.
2. Download every turn
def get_transcript(ticker, fiscal_year, fiscal_quarter):
path = f"/stocks/{ticker}/earnings-calls/{fiscal_year}/{fiscal_quarter}/speakers"
turns, offset = [], 0
while True:
page = get(path, limit=200, offset=offset)
turns += page["data"]
if not page["hasMore"]:
return page["eventTitle"], turns
offset += page["turnCount"]
title, turns = get_transcript("NVDA", 2027, 1)
print(title, len(turns), "turns")
Nvidia Corp Q1 FY2027 Earnings Call 31 turns
turnCount is the number of turns in this page and hasMore says whether more remain, so the loop moves the offset forward until it's done. Each turn has speakerName, speakerRole, text, and startSeconds and endSeconds, its position in the call audio.
One detail that matters for analysis: speakerName is filled only when the speaker's identity is verified. Otherwise it's None, and you get a numbered speakerIndex plus a role label when one is known. That's why the next step groups by role.
3. Who did the talking?
words = Counter()
for t in turns:
# Analyst roles carry the firm after a dash, so keep the part before it
role = (t["speakerRole"] or "Unknown").split(" \u2014 ")[0]
words[role] += len(t["text"].split())
total = sum(words.values())
for role, n in words.most_common():
print(f"{n / total:>4.0%} {n:>6,} words {role}")
53% 4,244 words CEO
29% 2,331 words CFO
10% 830 words Analyst
4% 335 words Head of Investor Relations
4% 283 words Operator
The CEO and CFO spoke 82% of the words on the call. The eight analysts who asked questions got 10% between them.
4. The analyst Q&A
A second endpoint groups the Q&A by analyst: everything one analyst said in an exchange, their firm, and who answered.
qa = get("/stocks/NVDA/earnings-calls/2027/1/questions")
for q in qa["data"]:
firm = q["firm"]["name"] if q["firm"] else "unknown firm"
answered_by = ", ".join(a["name"] for a in q["answerers"])
print(f"{q['number']}. {q['analyst']['name']} ({firm}) -> {answered_by}")
1. Joseph Moore (Morgan Stanley) -> Jensen Huang
...
6. Stacy Rasgon (Bernstein Research) -> Jensen Huang
7. Jim Schneider (Goldman Sachs) -> Jensen Huang
Jensen Huang answered seven of the eight questions alone. Only the last one went to the CFO and the head of investor relations as well.
5. Read one full exchange
startTurnIndex and endTurnIndex (exclusive) are positions in the call's turn list, so they slice the transcript you already downloaded:
first = qa["data"][0]
for t in turns[first["startTurnIndex"]:first["endTurnIndex"]]:
who = t["speakerName"] or t["speakerRole"]
print(f"[{who}] {t['text'][:120]}")
[Operator] Thank you. At this time, I would like to remind everyone, in order to ask a question, press star, then the number one on
[Joseph Moore] Great. Thank you for letting me ask the question. I guess I'd like to ask, what drove the change in segmentation? What's
[Jensen Huang] Yeah, thanks, Joe. First of all, Colette meant to say we're increasing our quarterly dividends from one cent to 25 cents
If you haven't downloaded the whole call, pass startTurnIndex as the offset of the speakers request instead.
A few things worth knowing
- The speakers response also has a
correctionslist: figures spoken on the call that the company's filings contradict. The transcript text itself is never edited, so a conflict travels next to it. This call has none. - Conferences and investor days have no fiscal quarter. For those, take the event
idfrom the investor-events list and request/v1/investor-events/{id}/speakers. - Every request above counts once against the daily limit, so this whole walkthrough costs three.
What do you measure on earnings calls? I'm curious what people track besides sentiment scores.
Top comments (0)