Earnings Call Transcript API Documentation
REST API reference for earnings call transcripts, speaker segments, company data, and full-text search across 250,000+ calls from 12,700+ companies worldwide. Below: authentication, endpoints, rate limits, error handling, and copy-paste code examples.
API Endpoints
Every endpoint has its own reference page with parameters, an example request and related endpoints. 21 endpoints across the API:
- GET Full-Text Search
/api/v1/search/ - GET Aggregate Search by Ticker
/api/v1/search/by_ticker
- GET Get Full Transcript
/api/v1/transcripts/{earningsId} - GET Get Transcript Summary
/api/v1/transcripts/{earningsId}/summary - GET Get Transcript Components
/api/v1/transcripts/{earningsId}/components - GET Recently Added Transcripts
/api/v1/transcripts/recent
- GET Get Speaker Segments
/api/v1/speakers/{earningsId}
- GET Get Latest Earnings Calls
/api/v1/earnings/latest - GET Get Upcoming Earnings Calls
/api/v1/earnings/upcoming - GET List Earnings Calls
/api/v1/earnings/ - GET Get Earnings Call Details
/api/v1/earnings/{id}
- GET List Companies
/api/v1/companies/ - GET Get Company by Ticker
/api/v1/companies/ticker/{ticker} - GET Get Latest Call by Ticker
/api/v1/companies/ticker/{ticker}/latest - GET Get Company by Name
/api/v1/companies/{name}
- GET List Sectors
/api/v1/sectors - GET List Industries
/api/v1/industries - GET List Exchanges
/api/v1/exchanges - GET List Event Types
/api/v1/event-types - GET Database Statistics
/api/v1/stats - GET Authenticated User Info
/api/v1/me
Authentication
All API requests require an API key passed via the X-API-Key header or api_key query parameter.
X-API-Key: YOUR_API_KEY
- Subscribe to a plan on earningsapi.io
- Your API key is shown after checkout and sent to your email
- Log in to your Dashboard to view or regenerate your key
Base URL
https://earningsapi.io/api/v1
All endpoints are prefixed with /api/v1. Responses are JSON with consistent pagination.
Rate Limits
All plans are billed quarterly and include the full API surface - tiers only differ in volume.
Managing Your Subscription
- Open your Dashboard to view your API key, regenerate it, see your usage, or cancel your plan.
- Billing runs through Paddle. Invoices arrive automatically with every payment and are issued in the name of DREAVERR Digital Solutions LLP (the legal entity behind earningsapi.io). Update your business name, address or VAT/tax ID from the "Update billing details" link inside any Paddle email.
- To switch tier (Basic ↔ Pro ↔ Ultra): cancel the current plan from your Dashboard first, then subscribe to the new tier on the pricing section. The new tier becomes active immediately; the old plan keeps running in parallel until the end of its billing quarter - no double charge, just a short overlap.
Stuck? Email contact@earningsapi.io with your invoice number and we'll sort it out.
Error Handling
The API uses standard HTTP status codes. Error responses include a message:
{
"error": "Forbidden",
"message": "Full transcripts require a Pro or Enterprise plan"
}
200 Success400 Bad Request - Invalid parameters401 Unauthorized - Missing or invalid API key403 Forbidden - Insufficient tier for this endpoint404 Not Found - Resource doesn't exist429 Rate Limited - Daily quota exceeded500 Server Error - Something went wrong on our endRecipes
Practical patterns for common workflows. Pick the one closest to your use case - they all use the same authentication and base URL from the Getting Started sections above.
Search Syntax
The q parameter accepts Google-style operators. PostgreSQL handles stemming automatically, so guidance matches guidance, guidances, guided.
# Implicit AND between words (both must appear)
GET /api/v1/search?q=agentic+AI
# Exact phrase - words must appear in this order
GET /api/v1/search?q=%22raised+guidance%22
# Boolean OR
GET /api/v1/search?q=agentic+OR+autonomous
# Negation (exclude word)
GET /api/v1/search?q=agentic+-human
# Combine - phrase + negation
GET /api/v1/search?q=%22raised+guidance%22+-macro
Get the latest call for a ticker
Two endpoints depending on what you need. /companies/ticker/:ticker/latest returns just the most recent call metadata. /companies/ticker/:ticker returns the full call history.
Ticker matching is exact (case-insensitive) and tolerates exchange suffixes - a query for ALAB matches ALAB, ALAB:US and ALAB.NS, but never substrings like IPCALAB. Every response item carries company_ticker, stock_symbol, exchange, mic and country so the result is self-validating.
curl "https://earningsapi.io/api/v1/companies/ticker/NVDA/latest" \
-H "X-API-Key: $EARNINGSAPI_KEY"
import requests, os
H = {"X-API-Key": os.environ["EARNINGSAPI_KEY"]}
r = requests.get(
"https://earningsapi.io/api/v1/companies/ticker/NVDA",
headers=H,
)
data = r.json()["data"]
print(f"{data['company_name']} - {len(data['earnings_calls'])} calls available")
for call in data["earnings_calls"][:5]:
print(f" {call['event_date_time'][:10]} {call['transcript_title']}")
Disambiguating tickers across listing venues
Short symbols collide across exchanges. PNB is a US bank on NYSE and Punjab National Bank on NSE India. BMW is in Frankfurt; SAP is in both Frankfurt and as an ADR on NYSE. To pin a listing venue, pass any of three optional query parameters on the /companies/ticker/:ticker, /companies/ticker/:ticker/latest and /earnings/?ticker= endpoints:
mic=XNAS- ISO 10383 Market Identifier Code. Resolves to the exchange's canonical name(s) in our DB.exchange=NASDAQ- exact, case-insensitive match against the underlying exchange name.country=US- ISO-3166 alpha-2.
curl "https://earningsapi.io/api/v1/companies/ticker/PNB/latest?mic=XNYS" \
-H "X-API-Key: $EARNINGSAPI_KEY"
curl "https://earningsapi.io/api/v1/earnings/?ticker=ALAB&mic=XNAS&limit=50" \
-H "X-API-Key: $EARNINGSAPI_KEY"
Supported MIC codes include the full ISO-10383 standard set for major venues: XNAS, XNYS, XASE, BATS, OTCM, XTSE, XTSX, NEOE, BVMF, XBUE, XBVM, XLON, AIMX, XDUB, XAMS, XBRU, XLIS, XPAR, XOSL, XETR, XFRA, XSWX, XWBO, XSTO, XCSE, XHEL, XICE, XMIL, XMAD, XATH, XWAR, XPRA, XBUD, XBSE, MISX, XIST, XTAE, XSAU, DSMD, XADS, XDFM, XJSE, XNSA, XTKS, XHKG, XKRX, XKOS, XASX, XNZE, XSES, XSHG, XSHE, XBOM, XNSE, XTAI, XIDX, XKLS, XPHS, XBKK and more. Unknown MICs return 400 Bad Request with the ISO reference URL.
Find what a CEO / CFO / analyst said
The type=speakers + speaker_type filters return only segments spoken by a specific role across the corpus. Combine with ticker to scope to one company.
GET /api/v1/search
?q=%22raised+guidance%22
&type=speakers
&speaker_type=executive
&ticker=NVDA
&limit=20
Valid speaker_type values: executive, analyst, operator, attendee, shareholder, unknown.
Cross-company aggregate - how many of [N tickers] mentioned X
The /search/by_ticker endpoint runs one SQL query that groups results per ticker. Pass a comma-separated tickers list to scope to a known universe (Fortune 500, S&P 500, Mag 7).
GET /api/v1/search/by_ticker
?q=agentic+AI
&tickers=AAPL,MSFT,NVDA,GOOGL,META,AMZN,TSLA,JPM,V,...
&date_from=2026-01-01
&date_to=2026-04-30
&limit=500
# response
{
"tickers_scoped": 500,
"total_companies": 184, // grand total - invariant of `limit`
"total_calls": 192, // grand total - invariant of `limit`
"tickers_with_mentions": 184, // legacy alias = total_companies
"total_calls_matched": 192, // legacy alias = total_calls
"results_count": 184, // rows actually returned (<= limit)
"results": [
{ "ticker": "NOW", "company_name": "ServiceNow", "sector": "Technology",
"calls_matched": 1, "last_match_date": "2026-04-23",
"earnings_ids": ["..."] },
...
]
}
total_companies / total_calls are the true grand totals over the full matching set and stay constant whether you pass limit=5 or limit=500. The results array is the top-N leaderboard, capped at limit. One request instead of 500 - built for cross-sectional NLP analyses.
Incremental sync (cursor-based polling)
For daily ETL jobs that pick up new calls without duplicates. The /transcripts/recent endpoint returns a next_after_id cursor - pass it back in subsequent requests.
import requests, json, os, pathlib
STATE = pathlib.Path("./.last_id.json")
H = {"X-API-Key": os.environ["EARNINGSAPI_KEY"]}
# Load last seen cursor
last = json.loads(STATE.read_text()) if STATE.exists() else {}
after_id = last.get("after_id", 0)
while True:
r = requests.get(
"https://earningsapi.io/api/v1/transcripts/recent",
params={"after_id": after_id, "limit": 100},
headers=H,
).json()
for call in r["data"]:
process(call) # -> push to your DB / vector store / queue
if not r["pagination"]["has_more"]:
break
after_id = r["pagination"]["next_after_id"]
STATE.write_text(json.dumps({"after_id": after_id}))
Idempotent - safe to re-run. The cursor advances only on successful pages, so a crash mid-sync resumes from the right place.
Compare a company's last N quarters
Use the company endpoint's earnings_calls array (sorted newest first) to get the last N call IDs, then fetch summaries in parallel.
import requests, os
from concurrent.futures import ThreadPoolExecutor
H = {"X-API-Key": os.environ["EARNINGSAPI_KEY"]}
BASE = "https://earningsapi.io/api/v1"
# 1. Get all calls for ticker (sorted desc)
calls = requests.get(f"{BASE}/companies/ticker/NVDA", headers=H).json()
last4 = calls["data"]["earnings_calls"][:4]
# 2. Fetch summaries in parallel
def summary(call):
r = requests.get(f"{BASE}/transcripts/{call['id']}/summary", headers=H)
return r.json()
with ThreadPoolExecutor(max_workers=4) as ex:
summaries = list(ex.map(summary, last4))
for c, s in zip(last4, summaries):
print(f"\n=== {c['event_date_time'][:10]} ===")
print(s.get("data", {}).get("summary", "")[:300])
RAG ingest - speaker-segmented chunks
Speaker segments map 1:1 to embedding chunks. No splitting heuristics needed. The pattern below ingests 100 newest calls - repeat with cursor-based polling for ongoing sync.
import requests, os
from openai import OpenAI
H = {"X-API-Key": os.environ["EARNINGSAPI_KEY"]}
BASE = "https://earningsapi.io/api/v1"
client = OpenAI()
calls = requests.get(f"{BASE}/transcripts/recent?limit=100", headers=H).json()["data"]
for call in calls:
segs = requests.get(
f"{BASE}/speakers/{call['earnings_call_id']}",
headers=H,
).json()["data"]
for seg in segs:
emb = client.embeddings.create(
model="text-embedding-3-small",
input=seg["text_content"],
).data[0].embedding
upsert_to_vector_db(
id=f"{call['earnings_call_id']}-{seg['component_order']}",
vector=emb,
metadata={
"ticker": call["company_ticker"],
"company": call["company_name"],
"date": call["event_date_time"],
"speaker": seg["speaker_name"],
"role": seg["speaker_type"], # executive / analyst / operator
"sector": call["sector"],
},
text=seg["text_content"],
)
Connect Claude Desktop via MCP
Skip writing API client code entirely - connect via our MCP server and ask Claude in natural language. Generate a personal connector URL from your dashboard, then paste into claude_desktop_config.json:
{
"mcpServers": {
"earningsapi": {
"url": "https://earningsapi.io/u/mct_xxx_your_token_xxx/mcp"
}
}
}
18 tools available: search_transcripts, count_mentions_by_ticker, get_latest_call, get_speakers, get_call_summary, list_earnings, and more. Full setup guide →