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:

PoolWhat countsReset
Transcript pullsGET /v1/events/{event_id}/segments via API keyDaily (00:00 UTC) + monthly (1st of month UTC)
SearchesGET /v1/segments/search via API keyDaily (00:00 UTC)

All other endpoints are unmetered.

Plan behavior

PlanWeb (JWT)API transcript pullsAPI searchesMonthly transcript cap
free15-segment preview + 3 unlocks/dayAccess only to events unlocked on web that day10/dayGoverned by unlock flow
trialUnlimited5/day50/day150/month
proUnlimited15/day150/day450/month
maxUnlimited30/day300/day900/month
enterpriseUnlimitedContract-definedContract-definedContract-defined

Quota rules

  • Transcript pulls are deduped by event_id within 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.
  • 429 responses include Retry-After; daily caps also include X-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; null when 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

StatusMeaning
400Invalid request parameters
401Missing/invalid API key or JWT
403Authenticated but forbidden (account state, scope, or free-plan gate)
404Resource not found
409Conflict (for example, max active keys reached)
422Validation error
429Quota exceeded

Endpoint Reference

Health

GET /health

Public health endpoint.

{ "status": "ok" }

Events

GET /v1/events

List events with optional filters.

Query parameters

ParamTypeNotes
qstringTitle full-text search (multi-word = AND semantics)
event_typestringe.g. remarks, press_conference, rally, interview, debate
speaker_idUUIDPrimary speaker filter
topic_slugstringTopic filter (e.g. nato)
citystringPartial match
state_codestringTwo-letter state code
start_datedatestarts_at >= start_date
end_datedatestarts_at <= end_date
has_transcriptbooltrue transcripts only, false stubs only
transcript_statusstringnone, pending, poor_audio, unlabeled, good
limitint1-200 (default 50)
cursorstringPagination 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

ParamTypeNotes
topic_slugstringOnly segments tagged with this topic
raw_speaker_idintDiarization speaker index
speaker_idUUIDResolved speaker filter
limitint1-500 (default 100)
cursorstringPagination cursor
unlockboolFree 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

ParamType
qstring
event_idUUID
speaker_idUUID
raw_speaker_idint
topic_slugstring
event_typestring
start_datedate
end_datedate
limitint (1-200, default 50)
cursorstring

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

ParamTypeDefault
hierarchyboolfalse
active_onlybooltrue
  • hierarchy=false: flat list
  • hierarchy=true: top-level topics with nested children

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

ParamType
qstring
event_idUUID
speaker_idUUID
event_typestring
start_datedate
end_datedate
limitint (1-200, default 50)
cursorstring

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

TypeRequired config objectFields
word_frequencyword_frequencyword (required), count_mode, bucket, chart_type, start_date, end_date, speaker_ids
speaker_activityspeaker_activityspeaker_id (required), bucket, chart_type, start_date, end_date
topic_trendtopic_trendtopic_id (required), bucket, chart_type, start_date, end_date, speaker_ids
word_streakword_streakword (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} — update name and/or config
  • 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: null means unlimited for that dimension.
  • monthly_cap, tokens_used, tokens_remaining are compatibility aliases for monthly transcript fields.

GET /v1/pulls

List transcript pull history for successful API-key transcript fetches.

Query parameters

ParamTypeNotes
limitint1-200 (default 50)
offsetintdefault 0
api_key_idUUIDoptional, 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

ToolDescription
search_segmentsFull-text transcript search with speaker, topic, date, and event type filters
get_eventRetrieve event metadata and full transcript
list_eventsBrowse events with filtering and pagination
list_speakersBrowse speakers with optional type filter
get_speaker_statsAggregate speaking time and Q&A statistics
list_topicsBrowse topic taxonomy (flat or hierarchical)
analyze_frequencyWord and topic frequency time-series analysis
get_usageCheck current plan quotas and usage counters
get_orderbookHistorical 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

  1. GET /v1/events?has_transcript=true
  2. GET /v1/events/{event_id}/segments
  3. If unlock_required=true, call again with ?unlock=true

Global search UI

  1. GET /v1/segments/search?q=...
  2. Apply filters (topic_slug, speaker_id, event_type, date range)
  3. Continue with next_cursor

Usage dashboard

  1. GET /v1/usage for counters/caps
  2. GET /v1/pulls for stale transcript detection