Mesh API
Public REST API for querying political speech, transcript segments, speakers, topics, usage, API keys, and saved analyses.
Base URL: https://api.meshapi.app
Interactive reference: https://api.meshapi.app/docs
Use this as the source for public API documentation on the Mesh frontend.
curl "https://api.meshapi.app/v1/events?limit=10&has_transcript=true" \
-H "X-API-Key: mesh_your_key_here"
All /v1/* endpoints require authentication except GET /health.
Pass your API key in the X-API-Key header:
X-API-Key: mesh_your_key_here
Create and manage API keys from your account dashboard on the Mesh web app.
| Pool | What counts | Reset |
|---|---|---|
| Transcript pulls | GET /v1/events/{event_id}/segments, deduped by event per UTC day | Daily and monthly |
| Searches | GET /v1/segments/search | Daily |
| Other endpoints | Metadata, usage, topics, speakers, saved analyses | Unmetered |
| Plan | Daily transcript pulls | Daily searches | Monthly transcript cap |
|---|---|---|---|
| Free | 3/day | 10/day | 3/month |
| Trial (14 days) | 5/day | 50/day | 150/month |
| Pro | 15/day | 150/day | 450/month |
| Max | 30/day | 300/day | 900/month |
| Enterprise | Contract-defined | Contract-defined | Contract-defined |
Caps are account-wide. All of your API keys share the same daily and monthly budget. Caps reset at 00:00 UTC daily and on the first of each month.
Rate Limits
In addition to quota caps, all API-key requests are subject to a per-key request-per-minute rate limit:
| Plan | Requests per minute |
|---|---|
| Free / Trial / Pro / Max | 60 |
| Enterprise | 300 |
JWT (web) callers have a separate 120 req/min limit per user. Rate-limited requests return 429 with a Retry-After header.
List endpoints use a paginated envelope:
{
"data": [],
"next_cursor": "dGVzdA==",
"count": 50
}
Single-resource endpoints return the object directly. Errors return:
{ "detail": "Message describing the error" }
| Status | Meaning |
|---|---|
| 400 | Invalid parameter or request body |
| 401 | Missing, invalid, or expired credentials |
| 403 | Account disabled, resource outside key scope, or Free-plan event not unlocked |
| 404 | Resource not found |
| 409 | Conflict, such as max active keys reached |
| 422 | Validation error |
| 429 | Daily/monthly quota exceeded |
Quota errors include useful reset headers when applicable:
- —
Retry-After - —
X-Pull-Budget-Reset
Health
GET /health
Public health check.
{ "status": "ok" }
Events
GET /v1/events
List events. Use has_transcript=true to browse events with transcript data.
Common query params:
| Param | Type | Description |
|---|---|---|
q | string | Full-text search on event title |
event_type | string | Event type such as remarks, press_conference, rally, interview |
speaker_id | UUID | Filter by primary speaker |
speaker_ids | string | Comma-separated UUIDs (max 20). Returns events where ANY listed speaker is primary. Mutually exclusive with speaker_id. |
topic_slug | string | Filter by topic |
city | string | Partial city match |
state_code | string | Exact state code |
start_date | date | Events on or after this date |
end_date | date | Events on or before this date |
limit | int | Page size, default 50 |
cursor | string | Cursor from previous response |
GET /v1/events/{event_id}
Get one event.
GET /v1/events/{event_id}/segments
Get transcript segments for one event.
Common query params:
| Param | Type | Description |
|---|---|---|
limit | int | Page size |
cursor | string | Cursor from previous response |
speaker_id | UUID | Filter to one resolved speaker |
topic_slug | string | Filter to one topic |
unlock | boolean | Free-plan users can set unlock=true to spend one daily unlock |
Free-plan users may receive a preview response with unlock_required: true. Paid users and already-unlocked events return full access with unlock_required: false.
POST /v1/events/batch-segments
Pull transcripts for multiple events in a single request. Designed for bulk data collection workflows. Requires an API key (not available via JWT).
Request body:
{
"event_ids": ["uuid1", "uuid2", "uuid3"],
"limit_per_event": 500
}
| Field | Type | Description |
|---|---|---|
event_ids | UUID[] | Event UUIDs to pull transcripts for. Max 25 (enterprise) or 10 (other plans). |
limit_per_event | int | Max segments per event, 1–500, default 500. |
Response:
{
"results": [
{
"event_id": "uuid1",
"segments": [...],
"next_cursor": null,
"count": 142
}
],
"skipped": [
{ "event_id": "uuid3", "reason": "daily_cap_exceeded" }
],
"pulls_used": 2,
"pulls_remaining_daily": 13,
"pulls_remaining_monthly": 448
}
Each unique event counts as one transcript pull, deduped by event per UTC day. Events that would exceed the daily or monthly cap are returned in skipped with the reason — the request never 429s partially. If an event needs more than limit_per_event segments, the entry includes a next_cursor that can be passed to GET /v1/events/{event_id}/segments?cursor=...&limit=500 to continue paginating that event.
Not available on the Free plan. Possible skip reasons: daily_cap_exceeded, monthly_cap_exceeded, not_found, speaker_not_in_scope, before_scope_date_range, after_scope_date_range.
GET /v1/events/{event_id}/topics
List topics attached to an event.
GET /v1/events/{event_id}/resolutions
List Kalshi market resolutions linked to an event when available.
Segment Search
GET /v1/segments/search
Search transcript segments across events.
Common query params:
| Param | Type | Description |
|---|---|---|
q | string | Full-text segment search |
speaker_id | UUID | Filter by resolved speaker |
topic_slug | string | Filter by topic |
event_type | string | Filter by event type |
start_date | date | Event start date lower bound |
end_date | date | Event start date upper bound |
limit | int | Page size |
cursor | string | Cursor from previous response |
Free-plan search returns only preview segments plus events you have unlocked that day. Paid plans search the full corpus.
Speakers
GET /v1/speakers
List speakers.
GET /v1/speakers/{speaker_id}
Get one speaker.
GET /v1/speakers/{speaker_id}/events
List events where the speaker is primary.
GET /v1/speakers/{speaker_id}/stats
Get speaker-level statistics when available.
Topics
GET /v1/topics
List topics. Add hierarchy=true to return parent/child structure.
GET /v1/topics/{slug}
Get one topic.
GET /v1/topics/{slug}/segments
Browse transcript segments attached to one topic.
Usage
GET /v1/usage
Return your current plan, daily transcript pulls, daily searches, monthly transcript usage, and trial metadata. Does not consume quota.
Important fields:
| Field | Description |
|---|---|
plan | Current effective plan |
daily_transcript_pulls_used / daily_transcript_pulls_cap | Transcript pull usage today |
daily_searches_used / daily_searches_cap | Search usage today |
monthly_transcript_pulls_used / monthly_transcript_pulls_cap | Monthly transcript ceiling |
trial_started_at / trial_ends_at | Trial metadata, if applicable |
Pull History
GET /v1/pulls
List transcripts the authenticated user has fetched and whether Mesh has newer transcript data available.
Common query params:
| Param | Type | Description |
|---|---|---|
limit | int | Page size |
offset | int | Offset pagination value |
api_key_id | UUID | Optional key-specific history filter |
Use has_updates to show stale transcript pulls in a user dashboard.
API Keys
Manage your API keys from the Mesh web app. Keys can be created, listed, and revoked from your account dashboard.
The raw key value is shown once at creation. Store it securely. It cannot be retrieved again.
Analyses
POST /v1/analyses/run
Run supported analysis jobs over the transcript corpus.
POST /v1/analyses/word-streaks
Run word-streak analysis.
GET /v1/analyses
List saved analyses.
POST /v1/analyses
Save an analysis configuration.
GET /v1/analyses/{analysis_id}
Get one saved analysis.
PUT /v1/analyses/{analysis_id}
Update a saved analysis.
DELETE /v1/analyses/{analysis_id}
Delete a saved analysis.
Browse and Discover Events
List all transcribed events
curl "https://api.meshapi.app/v1/events?has_transcript=true&limit=50" \
-H "X-API-Key: mesh_your_key_here"
Results are paginated. Pass the next_cursor value from the response as ?cursor=... to fetch the next page. Repeat until next_cursor is null.
Filter events by speaker
curl "https://api.meshapi.app/v1/events?speaker_id=381194fe-4856-4945-9e49-9809be82e924&has_transcript=true" \
-H "X-API-Key: mesh_your_key_here"
Get speaker UUIDs from GET /v1/speakers.
Filter events by topic
curl "https://api.meshapi.app/v1/events?topic_slug=nato&has_transcript=true" \
-H "X-API-Key: mesh_your_key_here"
Browse available topic slugs with GET /v1/topics.
Filter events by date range
curl "https://api.meshapi.app/v1/events?start_date=2026-01-01&end_date=2026-03-31&has_transcript=true" \
-H "X-API-Key: mesh_your_key_here"
Dates are ISO format. Both start_date and end_date are optional and can be used independently.
Combine multiple filters
curl "https://api.meshapi.app/v1/events?speaker_id=381194fe-...&event_type=press_conference&start_date=2026-01-01&has_transcript=true" \
-H "X-API-Key: mesh_your_key_here"
All filters use AND logic. This returns only press conferences by that speaker from 2026 onward that have transcripts.
Pull Transcripts
Pull a full transcript
curl "https://api.meshapi.app/v1/events/70d5bc85-cd9b-4670-94fb-6d5a7433b1d0/segments?limit=500" \
-H "X-API-Key: mesh_your_key_here"
Use limit=500 (the maximum) to minimize round trips. If next_cursor is not null, pass it as ?cursor=...&limit=500 to get the next page. Each call counts as one transcript pull for quota purposes, deduped by event per UTC day.
Pull segments by speaker within a transcript
curl "https://api.meshapi.app/v1/events/70d5bc85-.../segments?speaker_id=381194fe-...&limit=500" \
-H "X-API-Key: mesh_your_key_here"
Returns only segments from that resolved speaker within the event. Useful for isolating one person's remarks in a multi-speaker transcript.
Pull segments by topic within a transcript
curl "https://api.meshapi.app/v1/events/70d5bc85-.../segments?topic_slug=nato&limit=500" \
-H "X-API-Key: mesh_your_key_here"
Returns only segments tagged with that topic within the event.
Search Across Events
Full-text search
curl "https://api.meshapi.app/v1/segments/search?q=tariffs&limit=50" \
-H "X-API-Key: mesh_your_key_here"
Multi-word queries use AND semantics: q=trade+tariffs matches segments containing both words. Each successful search call counts against your daily search quota.
Search within one speaker's transcripts
curl "https://api.meshapi.app/v1/segments/search?q=immigration&speaker_id=381194fe-...&limit=50" \
-H "X-API-Key: mesh_your_key_here"
Combines full-text search with a speaker filter. Only segments from events where that speaker is primary are returned.
Search within a topic
curl "https://api.meshapi.app/v1/segments/search?q=sanctions&topic_slug=nato&limit=50" \
-H "X-API-Key: mesh_your_key_here"
Returns segments that match the text query and are tagged with the specified topic.
Search within event type and date range
curl "https://api.meshapi.app/v1/segments/search?q=economy&event_type=remarks&start_date=2026-01-01&end_date=2026-06-01&limit=50" \
-H "X-API-Key: mesh_your_key_here"
Narrows search to a specific event type and time window. All search filters use AND logic.
Monitor Usage and Changes
Check quota usage
curl "https://api.meshapi.app/v1/usage" \
-H "X-API-Key: mesh_your_key_here"
Key fields to watch: daily_transcript_pulls_used vs daily_transcript_pulls_cap, daily_searches_used vs daily_searches_cap, and monthly_transcript_pulls_used vs monthly_transcript_pulls_cap. A _cap of null means unlimited. This endpoint never consumes quota.
Detect updated transcripts
curl "https://api.meshapi.app/v1/pulls?limit=100" \
-H "X-API-Key: mesh_your_key_here"
Returns every transcript you have previously pulled. Check the has_updates field on each entry: true means Mesh has newer transcript data since your last pull (re-diarized, speaker resolved, etc.). Re-fetch those transcripts to get the latest version. This endpoint never consumes quota.
Combined Workflows
These examples chain multiple endpoints together for real-world data collection tasks. They are written in Python for readability but the logic applies to any HTTP client. Enterprise users with contract-defined caps can adjust concurrency and batch sizes to match their quotas.
Bulk-pull all transcripts for a speaker (using batch endpoint)
Collect a speaker's event IDs, then use the batch endpoint to pull transcripts in chunks of up to 25 (enterprise) or 10 (other plans) per request.
import requests
BASE = "https://api.meshapi.app"
HEADERS = {"X-API-Key": "mesh_your_key_here"}
SPEAKER_ID = "381194fe-4856-4945-9e49-9809be82e924"
# 1. Collect every event ID for this speaker
event_ids = []
cursor = None
while True:
params = {
"speaker_id": SPEAKER_ID,
"has_transcript": "true",
"limit": 200,
}
if cursor:
params["cursor"] = cursor
resp = requests.get(f"{BASE}/v1/events", headers=HEADERS, params=params).json()
event_ids.extend(e["event_id"] for e in resp["data"])
cursor = resp.get("next_cursor")
if not cursor:
break
# 2. Batch-pull transcripts (25 at a time for enterprise, 10 for other plans)
BATCH_SIZE = 25
for i in range(0, len(event_ids), BATCH_SIZE):
batch = event_ids[i : i + BATCH_SIZE]
resp = requests.post(
f"{BASE}/v1/events/batch-segments",
headers=HEADERS,
json={"event_ids": batch, "limit_per_event": 500},
).json()
for entry in resp["results"]:
print(f"{entry['event_id']}: {entry['count']} segments")
for skip in resp["skipped"]:
print(f"Skipped {skip['event_id']}: {skip['reason']}")
if resp["pulls_remaining_daily"] == 0:
print("Daily cap reached — resume tomorrow")
break
Each unique event counts as one transcript pull per UTC day. Re-fetching the same event later in the day is free, so retries and incremental runs are safe. The batch response tells you exactly how many pulls remain so you can stop gracefully.
Pull transcripts for two speakers and merge by date
Use speaker_ids to fetch events from both speakers in a single paginated call, then batch-pull the transcripts.
SPEAKER_A = "381194fe-4856-4945-9e49-9809be82e924"
SPEAKER_B = "f47ac10b-58cc-4372-a567-0e02b2c3d479"
# 1. Fetch events for both speakers in one call using speaker_ids
events = []
cursor = None
while True:
params = {
"speaker_ids": f"{SPEAKER_A},{SPEAKER_B}",
"has_transcript": "true",
"limit": 200,
}
if cursor:
params["cursor"] = cursor
resp = requests.get(f"{BASE}/v1/events", headers=HEADERS, params=params).json()
events.extend(resp["data"])
cursor = resp.get("next_cursor")
if not cursor:
break
# Events come sorted by date (newest first) — already interleaved
event_ids = [e["event_id"] for e in events]
# 2. Batch-pull transcripts
BATCH_SIZE = 25
for i in range(0, len(event_ids), BATCH_SIZE):
batch = event_ids[i : i + BATCH_SIZE]
resp = requests.post(
f"{BASE}/v1/events/batch-segments",
headers=HEADERS,
json={"event_ids": batch, "limit_per_event": 500},
).json()
for entry in resp["results"]:
event = next(e for e in events if e["event_id"] == entry["event_id"])
speaker = event.get("primary_speaker_name", "Unknown")
print(f"[{speaker}] {event['canonical_title']}: {entry['count']} segments")
if resp["pulls_remaining_daily"] == 0:
break
Quota-aware bulk pulling
The batch endpoint returns pulls_remaining_daily and pulls_remaining_monthly in every response, so you can make cap-aware decisions without extra API calls. For finer control, check GET /v1/usage before starting.
def get_remaining_pulls():
usage = requests.get(f"{BASE}/v1/usage", headers=HEADERS).json()
daily_cap = usage["daily_transcript_pulls_cap"] # null = unlimited
monthly_cap = usage["monthly_transcript_pulls_cap"]
daily_left = (
daily_cap - usage["daily_transcript_pulls_used"]
if daily_cap is not None else float("inf")
)
monthly_left = (
monthly_cap - usage["monthly_transcript_pulls_used"]
if monthly_cap is not None else float("inf")
)
return min(daily_left, monthly_left)
budget = get_remaining_pulls()
print(f"Transcript pulls remaining: {budget}")
# Batch-pull only what fits in the remaining budget
BATCH_SIZE = 25
for i in range(0, len(event_ids), BATCH_SIZE):
batch = event_ids[i : i + BATCH_SIZE]
resp = requests.post(
f"{BASE}/v1/events/batch-segments",
headers=HEADERS,
json={"event_ids": batch},
).json()
for entry in resp["results"]:
print(f"{entry['event_id']}: {entry['count']} segments")
# The batch endpoint gracefully skips events over cap instead of 429-ing
if resp["skipped"]:
for skip in resp["skipped"]:
print(f"Skipped {skip['event_id']}: {skip['reason']}")
break
The usage endpoint never consumes quota. The batch endpoint never returns a 429 for partial over-cap — it processes what it can and skips the rest.
Build a topic corpus from search, then batch-pull full transcripts
Search for segments matching a topic across all speakers, collect the distinct event IDs from the results, then batch-pull the full transcript for each.
# 1. Search for all segments mentioning "tariffs" in press conferences
matched_event_ids = set()
cursor = None
while True:
params = {
"q": "tariffs",
"event_type": "press_conference",
"start_date": "2026-01-01",
"limit": 200,
}
if cursor:
params["cursor"] = cursor
resp = requests.get(
f"{BASE}/v1/segments/search", headers=HEADERS, params=params
).json()
for seg in resp["data"]:
matched_event_ids.add(seg["event_id"])
cursor = resp.get("next_cursor")
if not cursor:
break
print(f"Found segments in {len(matched_event_ids)} events")
# 2. Batch-pull the full transcript for each discovered event
event_list = list(matched_event_ids)
BATCH_SIZE = 25
for i in range(0, len(event_list), BATCH_SIZE):
batch = event_list[i : i + BATCH_SIZE]
resp = requests.post(
f"{BASE}/v1/events/batch-segments",
headers=HEADERS,
json={"event_ids": batch, "limit_per_event": 500},
).json()
for entry in resp["results"]:
print(f"{entry['event_id']}: {entry['count']} segments")
if resp["pulls_remaining_daily"] == 0:
break
Search calls count against the daily search quota, and each new event pulled counts against the transcript pull quota.
Detect and re-pull updated transcripts
Poll GET /v1/pulls to find transcripts that Mesh has re-processed since your last fetch, then batch-pull fresh copies.
# 1. Check for stale pulls
resp = requests.get(f"{BASE}/v1/pulls?limit=100", headers=HEADERS).json()
stale_ids = [p["event_id"] for p in resp["data"] if p.get("has_updates")]
print(f"{len(stale_ids)} transcripts have newer data")
# 2. Batch re-pull only the updated ones
BATCH_SIZE = 25
for i in range(0, len(stale_ids), BATCH_SIZE):
batch = stale_ids[i : i + BATCH_SIZE]
resp = requests.post(
f"{BASE}/v1/events/batch-segments",
headers=HEADERS,
json={"event_ids": batch, "limit_per_event": 500},
).json()
for entry in resp["results"]:
print(f"Refreshed {entry['event_id']}: {entry['count']} segments")
Re-pulling a previously fetched event on the same UTC day does not cost an additional transcript pull.
Extract Q&A from press conferences
Transcript segments include is_question and question_group_id fields. Pull a press conference transcript and separate questions from answers to analyze what journalists are asking a speaker about.
# 1. Find all press conferences for a speaker
events = []
cursor = None
while True:
params = {
"speaker_id": SPEAKER_ID,
"event_type": "press_conference",
"has_transcript": "true",
"limit": 200,
}
if cursor:
params["cursor"] = cursor
resp = requests.get(f"{BASE}/v1/events", headers=HEADERS, params=params).json()
events.extend(resp["data"])
cursor = resp.get("next_cursor")
if not cursor:
break
# 2. Pull each transcript and extract Q&A pairs
for event in events:
resp = requests.post(
f"{BASE}/v1/events/batch-segments",
headers=HEADERS,
json={"event_ids": [event["event_id"]], "limit_per_event": 500},
).json()
if not resp["results"]:
continue
segments = resp["results"][0]["segments"]
questions = [s for s in segments if s["is_question"]]
answers = [s for s in segments if not s["is_question"]]
print(f"{event['canonical_title']}: {len(questions)} questions, {len(answers)} answer segments")
# Group questions by question_group_id to see multi-sentence questions
from collections import defaultdict
groups = defaultdict(list)
for q in questions:
gid = q.get("question_group_id") or q["idx"]
groups[gid].append(q["sentence_txt"])
for gid, texts in groups.items():
print(f" Q: {' '.join(texts)}")
The question_group_id links multi-sentence questions into a single logical question. Segments without is_question in the same region are typically the speaker's answer.
Compare two speakers on a topic
Use the topic segments endpoint (GET /v1/topics/{slug}/segments) with different speaker filters to compare what two speakers say about the same subject. This endpoint is unmetered — it does not count as a transcript pull.
SPEAKER_A = "381194fe-4856-4945-9e49-9809be82e924"
SPEAKER_B = "f47ac10b-58cc-4372-a567-0e02b2c3d479"
TOPIC = "nato"
def get_topic_segments(topic_slug, speaker_id):
segments = []
cursor = None
while True:
params = {"speaker_id": speaker_id, "limit": 200}
if cursor:
params["cursor"] = cursor
resp = requests.get(
f"{BASE}/v1/topics/{topic_slug}/segments",
headers=HEADERS, params=params,
).json()
segments.extend(resp["data"])
cursor = resp.get("next_cursor")
if not cursor:
break
return segments
segs_a = get_topic_segments(TOPIC, SPEAKER_A)
segs_b = get_topic_segments(TOPIC, SPEAKER_B)
print(f"Speaker A on {TOPIC}: {len(segs_a)} segments across {len(set(s['event_id'] for s in segs_a))} events")
print(f"Speaker B on {TOPIC}: {len(segs_b)} segments across {len(set(s['event_id'] for s in segs_b))} events")
# Organize by event date for chronological comparison
for seg in sorted(segs_a, key=lambda s: s.get("event_date", "")):
print(f" [{seg.get('event_date', '')[:10]}] {seg['sentence_txt'][:120]}")
Since topic segment browsing is unmetered, this workflow consumes zero transcript pulls and zero searches — useful for exploratory analysis before deciding what to pull in full.
Correlate transcripts with prediction market outcomes
Pull events that have Kalshi market resolutions, then fetch both the transcript and the resolution data to analyze how speech correlates with market outcomes.
# 1. Find events with transcripts (market-linked events have kalshi_tickers)
events = []
cursor = None
while True:
params = {"has_transcript": "true", "limit": 200}
if cursor:
params["cursor"] = cursor
resp = requests.get(f"{BASE}/v1/events", headers=HEADERS, params=params).json()
events.extend(resp["data"])
cursor = resp.get("next_cursor")
if not cursor:
break
market_events = [e for e in events if e.get("kalshi_tickers")]
print(f"{len(market_events)} events have linked prediction markets")
# 2. For each market-linked event, fetch resolutions and transcript
for event in market_events:
eid = event["event_id"]
# Get market resolutions (unmetered)
resolutions = requests.get(
f"{BASE}/v1/events/{eid}/resolutions", headers=HEADERS
).json()
# Get transcript topics (unmetered)
topics = requests.get(
f"{BASE}/v1/events/{eid}/topics", headers=HEADERS
).json()
print(f"\n{event['canonical_title']}")
print(f" Topics: {', '.join(t['slug'] for t in topics)}")
for r in resolutions.get("resolutions", []):
print(f" Market: {r['market_ticker']} → {r['resolution']}")
# 3. Batch-pull the full transcripts for deeper analysis
event_ids = [e["event_id"] for e in market_events]
BATCH_SIZE = 25
for i in range(0, len(event_ids), BATCH_SIZE):
batch = event_ids[i : i + BATCH_SIZE]
resp = requests.post(
f"{BASE}/v1/events/batch-segments",
headers=HEADERS,
json={"event_ids": batch, "limit_per_event": 500},
).json()
for entry in resp["results"]:
print(f" {entry['event_id']}: {entry['count']} segments pulled")
Resolutions and topics are unmetered. Only the batch-segments call at the end consumes transcript pulls.
Build a speaker profile
Combine speaker details, aggregated stats, event history, and top topics into a comprehensive profile — useful for building speaker pages or research dossiers.
# 1. Speaker identity and stats (unmetered)
speaker = requests.get(
f"{BASE}/v1/speakers/{SPEAKER_ID}", headers=HEADERS
).json()
stats = requests.get(
f"{BASE}/v1/speakers/{SPEAKER_ID}/stats", headers=HEADERS
).json()
print(f"{speaker['canonical_name']} ({speaker['event_count']} events)")
speaking = stats["speaking_time"]
qa = stats["as_primary_speaker"]
print(f" Total speaking time: {speaking['total_speaking_ms'] / 60000:.0f} minutes")
print(f" Events appeared in: {speaking['events_appeared_in']}")
print(f" Q&A events: {qa['qa_event_count']}, avg questions: {qa['avg_questions_per_event']:.1f}")
# 2. All their events (unmetered)
events = requests.get(
f"{BASE}/v1/speakers/{SPEAKER_ID}/events", headers=HEADERS
).json()
# 3. Discover which topics this speaker covers most
from collections import Counter
topic_counts = Counter()
for event in events:
topics = requests.get(
f"{BASE}/v1/events/{event['event_id']}/topics", headers=HEADERS
).json()
for t in topics:
topic_counts[t["slug"]] += 1
print(f"\nTop topics:")
for slug, count in topic_counts.most_common(10):
print(f" {slug}: {count} events")
Every call in this workflow is unmetered — speaker details, stats, events, and event topics all fall outside the transcript pull and search quotas.
Track a topic over time
Use the topic segments endpoint with date ranges to measure how much a topic is discussed week-over-week or month-over-month.
from datetime import date, timedelta
TOPIC = "tariffs"
start = date(2026, 1, 1)
end = date(2026, 6, 1)
# Walk month by month
current = start
while current < end:
month_end = (current.replace(day=28) + timedelta(days=4)).replace(day=1)
if month_end > end:
month_end = end
segments = []
cursor = None
while True:
params = {
"start_date": current.isoformat(),
"end_date": month_end.isoformat(),
"limit": 200,
}
if cursor:
params["cursor"] = cursor
resp = requests.get(
f"{BASE}/v1/topics/{TOPIC}/segments",
headers=HEADERS, params=params,
).json()
segments.extend(resp["data"])
cursor = resp.get("next_cursor")
if not cursor:
break
event_ids = set(s["event_id"] for s in segments)
print(f"{current.strftime('%Y-%m')}: {len(segments)} segments across {len(event_ids)} events")
current = month_end
This workflow is entirely unmetered — topic segment browsing does not consume transcript pulls or searches. Useful for building dashboards or tracking topic salience before committing pull budget.
- —Search results are ordered by transcript position, not relevance.
- —Free-plan unlocks reset at 00:00 UTC.
- —The API key value is only visible once at creation. Store it immediately.
- —Caps are account-wide across all your API keys.