Mesh API Documentation
Developer reference for integrating with the Mesh REST API.
Base URL: https://api.meshapi.app
Interactive reference: https://api.meshapi.app/docs
Quick Start
curl "https://api.meshapi.app/v1/events?limit=10" \
-H "X-API-Key: mesh_your_key_here"
curl "https://api.meshapi.app/v1/events?limit=10" \
-H "Authorization: Bearer <supabase_jwt>"
Authentication
All /v1/* endpoints require auth except GET /health.
API Key (server-to-server)
Use header:
X-API-Key: mesh_...
Bearer JWT (logged-in web/app users)
Use header:
Authorization: Bearer <supabase_jwt>
JWT-only endpoints
/v1/keys endpoints are JWT-only (Authorization: Bearer ...).
Plans and Quotas
Mesh uses two API usage pools:
| Pool | What counts | Reset |
|---|---|---|
| Transcript pulls | GET /v1/events/{event_id}/segments via API key | Daily (00:00 UTC) + monthly (1st of month UTC) |
| Searches | GET /v1/segments/search via API key | Daily (00:00 UTC) |
All other endpoints are unmetered.
Plan behavior
| Plan | Web (JWT) | API transcript pulls | API searches | Monthly transcript cap |
|---|---|---|---|---|
free | 15-segment preview + 3 unlocks/day | Access only to events unlocked on web that day | 10/day | Governed by unlock flow |
trial | Unlimited | 5/day | 50/day | 150/month |
pro | Unlimited | 15/day | 150/day | 450/month |
max | Unlimited | 30/day | 300/day | 900/month |
enterprise | Unlimited | Contract-defined | Contract-defined | Contract-defined |
Quota rules
- —Transcript pulls are deduped by
event_idwithin the same UTC day. - —Monthly transcript usage is distinct events pulled in the current month.
- —Searches are counted per successful request (no dedupe).
- —Free plan API keys can only pull events unlocked via web that day.
- —Free plan search is clamped to first 15 segments per event, except unlocked events.
- —
429responses includeRetry-After; daily caps also includeX-Pull-Budget-Reset.
Response Conventions
Standard paginated envelope
{
"data": [],
"next_cursor": "dGVzdA==",
"count": 50
}
- —
count: number of items in this page - —
next_cursor: pass into?cursor=...to fetch next page;nullwhen done
Offset-style pagination (/v1/pulls)
/v1/pulls uses limit + offset.
The response still returns next_cursor, which is the next offset value.
Error shape
{ "detail": "..." }
Error Codes
| Status | Meaning |
|---|---|
400 | Invalid request parameters |
401 | Missing/invalid API key or JWT |
403 | Authenticated but forbidden (account state, scope, or free-plan gate) |
404 | Resource not found |
409 | Conflict (for example, max active keys reached) |
422 | Validation error |
429 | Quota exceeded |
Endpoint Reference
Health
GET /health
Public health endpoint.
{ "status": "ok" }
Events
GET /v1/events
List events with optional filters.
Query parameters
| Param | Type | Notes |
|---|---|---|
q | string | Title full-text search (multi-word = AND semantics) |
event_type | string | e.g. remarks, press_conference, rally, interview, debate |
speaker_id | UUID | Primary speaker filter |
topic_slug | string | Topic filter (e.g. nato) |
city | string | Partial match |
state_code | string | Two-letter state code |
start_date | date | starts_at >= start_date |
end_date | date | starts_at <= end_date |
has_transcript | bool | true transcripts only, false stubs only |
transcript_status | string | none, pending, poor_audio, unlabeled, good |
limit | int | 1-200 (default 50) |
cursor | string | Pagination cursor |
Response item fields
- —
event_id,canonical_title,event_type,starts_at - —
city,state_code,venue_name - —
source_url - —
primary_speaker_id,primary_speaker_name - —
has_transcript,transcript_status - —
duration,kalshi_ticker,num_questions,updated_at
GET /v1/events/{event_id}
Get one event by ID.
Returns 404 if not found.
GET /v1/events/{event_id}/segments
Get transcript segments for an event (ordered by idx).
Query parameters
| Param | Type | Notes |
|---|---|---|
topic_slug | string | Only segments tagged with this topic |
raw_speaker_id | int | Diarization speaker index |
speaker_id | UUID | Resolved speaker filter |
limit | int | 1-500 (default 100) |
cursor | string | Pagination cursor |
unlock | bool | Free web users only: true claims an unlock and returns full transcript |
Response
{
"data": [],
"next_cursor": null,
"count": 15,
"preview_truncated": true,
"total_segments": 238,
"unlock_required": true,
"daily_unlocks_remaining": 2
}
Segment fields
- —
segment_id,event_id,idx - —
start_time_ms,end_time_ms - —
raw_speaker_id,resolved_speaker_id,speaker_name - —
sentence_txt,is_question,question_group_id - —
topics(array of topic slugs)
Free web preview flow
- —Without
unlock=true: first 15 segments + preview metadata - —With
unlock=true: full transcript (if unlock budget available) - —After unlock, same-day refetch of the same event is free
GET /v1/events/{event_id}/topics
List topics associated with the event.
GET /v1/events/{event_id}/resolutions
Get Kalshi market resolutions for an event.
{
"event_id": "uuid",
"kalshi_ticker": "KX...",
"resolutions": [
{ "market_ticker": "KX...-T50", "strike": 50.0, "resolution": true }
]
}
Segments
GET /v1/segments/search
Cross-event full-text transcript search.
Query parameters
| Param | Type |
|---|---|
q | string |
event_id | UUID |
speaker_id | UUID |
raw_speaker_id | int |
topic_slug | string |
event_type | string |
start_date | date |
end_date | date |
limit | int (1-200, default 50) |
cursor | string |
Response item fields
- —
segment_id,event_id,idx - —
event_title,event_type,event_date - —
start_time_ms,end_time_ms - —
raw_speaker_id,resolved_speaker_id,speaker_name - —
sentence_txt,is_question,question_group_id - —
topics
Speakers
GET /v1/speakers
List speakers (alphabetical).
Optional query
- —
speaker_type
GET /v1/speakers/{speaker_id}
Get one speaker by UUID.
Returns 404 if not found.
GET /v1/speakers/{speaker_id}/events
List events where this speaker appears (primary speaker and/or resolved segments).
GET /v1/speakers/{speaker_id}/stats
Speaker aggregate statistics response:
{
"speaker_id": "uuid",
"as_primary_speaker": {
"qa_event_count": 0,
"avg_questions_per_event": 0,
"total_questions": 0
},
"speaking_time": {
"total_speaking_ms": 0,
"avg_segment_ms": 0,
"segment_count": 0,
"events_appeared_in": 0,
"primary_event_speaking_ms": 0,
"secondary_event_speaking_ms": 0
}
}
Topics
GET /v1/topics
List topic taxonomy.
Query parameters
| Param | Type | Default |
|---|---|---|
hierarchy | bool | false |
active_only | bool | true |
- —
hierarchy=false: flat list - —
hierarchy=true: top-level topics with nestedchildren
GET /v1/topics/{slug}
Get topic by slug.
Returns 404 if not found.
GET /v1/topics/{slug}/segments
List segments tagged with this topic.
Query parameters
| Param | Type |
|---|---|
q | string |
event_id | UUID |
speaker_id | UUID |
event_type | string |
start_date | date |
end_date | date |
limit | int (1-200, default 50) |
cursor | string |
Returns 404 if topic slug does not exist.
Analyses
POST /v1/analyses/run
Execute an analysis and return a time series.
Body shape
{
"analysis_type": "word_frequency",
"word_frequency": {
"word": "economy",
"count_mode": "segments",
"bucket": "month",
"chart_type": "line",
"start_date": "2026-01-01T00:00:00Z",
"end_date": "2026-12-31T23:59:59Z",
"speaker_ids": ["uuid"]
}
}
analysis_type values:
- —
word_frequency - —
speaker_activity - —
topic_trend - —
word_streak
bucket values:
- —
day,week,month,quarter
count_mode values:
- —
segments(count segments containing the term) - —
occurrences(count total term occurrences)
chart_type values:
- —
line - —
bar
Config by analysis type
| Type | Required config object | Fields |
|---|---|---|
word_frequency | word_frequency | word (required), count_mode, bucket, chart_type, start_date, end_date, speaker_ids |
speaker_activity | speaker_activity | speaker_id (required), bucket, chart_type, start_date, end_date |
topic_trend | topic_trend | topic_id (required), bucket, chart_type, start_date, end_date, speaker_ids |
word_streak | word_streak | word (required), count_mode, bucket, chart_type, start_date, end_date, speaker_ids |
Response
{
"analysis_type": "word_frequency",
"chart_type": "line",
"series": [
{ "bucket_start": "2026-01-01T00:00:00Z", "value": 42 }
]
}
POST /v1/analyses/word-streaks
Compute streak metadata and return gap-filled series.
Body
{
"word": "tariff",
"count_mode": "segments",
"bucket": "week",
"chart_type": "bar",
"start_date": "2026-01-01T00:00:00Z",
"end_date": "2026-12-31T23:59:59Z",
"speaker_ids": ["uuid"]
}
Response
{
"word": "tariff",
"bucket": "week",
"chart_type": "bar",
"current_streak": 3,
"longest_streak": 6,
"streak_periods": [
{ "start": "2026-01-01T00:00:00Z", "end": "2026-02-12T00:00:00Z", "length": 6 }
],
"series": [
{ "bucket_start": "2026-01-01T00:00:00Z", "value": 1 }
]
}
Saved analyses
- —
GET /v1/analyses— list saved analyses - —
POST /v1/analyses— create saved analysis - —
GET /v1/analyses/{analysis_id}— get one - —
PUT /v1/analyses/{analysis_id}— updatenameand/orconfig - —
DELETE /v1/analyses/{analysis_id}— delete (204)
Create payload:
{
"name": "Economy trend",
"analysis_type": "word_frequency",
"config": { "word": "economy", "bucket": "month" }
}
Usage and Pull History
GET /v1/usage
Return current plan + usage counters.
{
"plan": "pro",
"monthly_cap": 450,
"tokens_used": 12,
"tokens_remaining": 438,
"period_start": "2026-04-01T00:00:00Z",
"period_end": "2026-05-01T00:00:00Z",
"daily_transcript_pulls_used": 2,
"daily_transcript_pulls_cap": 15,
"monthly_transcript_pulls_used": 12,
"monthly_transcript_pulls_cap": 450,
"daily_searches_used": 9,
"daily_searches_cap": 150,
"web_daily_event_unlocks_used": null,
"web_daily_event_unlocks_cap": null,
"trial_started_at": null,
"trial_ends_at": null
}
Notes:
- —
*_cap: nullmeans unlimited for that dimension. - —
monthly_cap,tokens_used,tokens_remainingare compatibility aliases for monthly transcript fields.
GET /v1/pulls
List transcript pull history for successful API-key transcript fetches.
Query parameters
| Param | Type | Notes |
|---|---|---|
limit | int | 1-200 (default 50) |
offset | int | default 0 |
api_key_id | UUID | optional, filter to one key |
Response
{
"data": [
{
"event_id": "uuid",
"canonical_title": "Title",
"event_type": "remarks",
"starts_at": "2026-04-12T00:00:00Z",
"last_pulled_at": "2026-04-19T10:22:00Z",
"transcript_updated_at": "2026-04-20T09:10:00Z",
"has_updates": true
}
],
"next_cursor": "50",
"count": 1
}
Key Management (/v1/keys, JWT only)
GET /v1/keys
List active keys. Raw secret is never returned here.
Response entries include:
- —
id,label,is_active,created_at,last_used_at,key_prefix - —optional
scope(speaker/date/cap restrictions) - —optional
usage(per-key daily/monthly counters and caps)
POST /v1/keys
Create new key.
{ "label": "my app" }
Response returns raw key once:
{
"id": "uuid",
"label": "my app",
"is_active": true,
"created_at": "2026-04-20T00:00:00Z",
"last_used_at": null,
"key_prefix": "mesh_abcd1234",
"key": "mesh_abcd1234..."
}
DELETE /v1/keys/{key_id}
Revoke key (204 on success).
MCP Server
The Mesh MCP server lets AI assistants (Claude, Cursor, Windsurf, and any MCP-compatible client) access the full Mesh API natively — search transcripts, browse events and speakers, run analytics, and query orderbook data through natural language.
Install
npx mesh-mcp-server install
The installer will prompt for your API key and write the configuration to your MCP client's config file automatically.
Manual configuration
Add to your MCP client config (e.g. claude_desktop_config.json, .cursor/mcp.json):
{
"mcpServers": {
"mesh": {
"command": "npx",
"args": ["-y", "mesh-mcp-server"],
"env": {
"MESH_API_KEY": "mesh_sk_..."
}
}
}
}
Available tools
| Tool | Description |
|---|---|
search_segments | Full-text transcript search with speaker, topic, date, and event type filters |
get_event | Retrieve event metadata and full transcript |
list_events | Browse events with filtering and pagination |
list_speakers | Browse speakers with optional type filter |
get_speaker_stats | Aggregate speaking time and Q&A statistics |
list_topics | Browse topic taxonomy (flat or hierarchical) |
analyze_frequency | Word and topic frequency time-series analysis |
get_usage | Check current plan quotas and usage counters |
get_orderbook | Historical orderbook snapshots for prediction markets |
Example usage
Once connected, you can ask your AI assistant questions like:
- —"What did Trump say about tariffs in the last 30 days?"
- —"Find all press conferences where NATO was discussed"
- —"Show me the word frequency trend for 'inflation' over the past year"
- —"How much API quota do I have left today?"
The MCP server translates these into the appropriate Mesh API calls and returns structured results.
Common Integration Flows
Fetch and render a transcript
- —
GET /v1/events?has_transcript=true - —
GET /v1/events/{event_id}/segments - —If
unlock_required=true, call again with?unlock=true
Global search UI
- —
GET /v1/segments/search?q=... - —Apply filters (
topic_slug,speaker_id,event_type, date range) - —Continue with
next_cursor
Usage dashboard
- —
GET /v1/usagefor counters/caps - —
GET /v1/pullsfor stale transcript detection