[BidClub_]

Query the entire library

Every published episode — metadata, TL;DR, digest, full transcript, bilingual editorial fields, Assets / Sector / Focus tags, participants, and provenance — plus the show registry, as JSON or portable files. The BidClub API and downloads require no account and no API key.

Fastest path: use the complete catalog endpoint to discover every slug, then fetch full episodes with modest parallelism. Use the versioned API for common reads, file routes for individual research artifacts, or the public read-only PostgREST endpoint for custom queries. JSON and download routes support cross-origin browser requests. Cache policies are listed below.

This page runs top to bottom from the smallest surface to the largest: the REST endpoints, then the discovery files a machine reads on its own, then agent installs, then feeds for human readers, and finally direct database access for queries the REST layer does not express.

Endpoints

GET /api/v1/shows
    show registry: identity, language, hosts, tracking state, source URLs

GET /api/feed-index
    complete compact catalog in one JSON response: count + every published episode

GET /api/feed-index/show/{show_id}
GET /api/feed-index/person/{person}
    same shape, scoped to one show or exact URL-encoded participant name

GET /api/v1/episodes?show={show_id}&lang={EN|ZH}&limit={1..100}&offset={0..}
    paginated newest episode metadata; default limit 50 and offset 0
    pagination.next_offset is null on the final page
    optional asset=...&sector=...&focus=... filters

GET /api/v1/episodes/{slug}
    one full episode: editorial markdown, transcript, alternates, provenance

GET /api/v1/search?q={query}&show=...&person=...&lang=...&len=...&time=...
    deep full-library search; up to 20 newest matches
    optional asset=...&sector=...&focus=... filters
    returned fallback matches carry partial: true; see search limits

GET /dl/{slug}/{summary|transcript|full}.{md|txt|pdf}
    one episode artifact

GET /dl/show/{show_id}/{summary|transcript|full}.{md|txt|pdf}
    one ZIP of the show's available published artifacts; see download limits

Grab everything

No account, cookie, or BidClub API key is required. The catalog endpoint returns every published episode, including untracked shows, as compact metadata in one request. Each slug resolves to the complete JSON record, including available editorial Markdown and transcript. Use /api/v1/episodes for pagination or tag filters; feed-index routes do not accept query filters.

# download the complete compact catalog (currently a few MB)
curl -o bidclub-catalog.json "https://bidclub.ai/api/feed-index"

# fetch every complete episode JSON with modest parallelism
jq -r '.episodes[].slug' bidclub-catalog.json |
  xargs -P 6 -I {} sh -c   'curl -fsS "https://bidclub.ai/api/v1/episodes/{}" -o "{}.json"'

# browser / JavaScript: no auth header required
const catalog = await fetch("https://bidclub.ai/api/feed-index").then(r => r.json());

Episode tags

Three independent groups: Assets describes the asset class, Sector the industry, and Focus the discussion's purpose. Each array contains zero to two stable IDs, primary first; keep both when two are returned. These are the same tags used in the feed, article sidebar, and grouped filters. Empty groups are intentional when nothing clearly applies. Other combines commodities, fixed income, FX, and other asset classes. Participant chips remain separate; there is no company or ticker bucket.

asset → tag_assets
  equities           Equities
  crypto             Crypto
  vc_pe              VC/PE
  other              Other

sector → tag_sectors
  ai_software        AI & Software
  semis              Semis
  robotics           Robotics
  biotech            Biotech
  consumer           Consumer
  finance            Finance
  blockchain         Blockchain
  energy             Energy
  space_defense      Space & Defense

focus → tag_focus
  investing          Investing
  company_building   Company Building
  technical          Technical
  macro              Macro
  policy             Policy

Use asset, sector, and focus on the episode list and search endpoints. Comma-separated IDs match any selected tag within a group; supplied groups must all match. For example, asset=equities,vc_pe&sector=ai_software means (Equities OR VC/PE) AND AI & Software. Either a primary or secondary tag can match. Omit a group, or send all its IDs, to include everything in that group, including episodes with no tag. The literal none selects nothing and returns no episodes; it does not find untagged episodes. Unknown IDs return 400.

tagging_status is classified, pending, failed, or skipped; tagging_version identifies the taxonomy revision. classified with an empty array is a valid result, not a failed backfill. Tags are generated after the source TL;DR and digest. Changes to the source title, language, TL;DR, or digest invalidate the previous classification unless a replacement is published with the edit; Wireroom retries pending or outdated published episodes during refresh and backfill.

Search filters

Search uses the full-text index across titles, descriptions, TL;DRs, digests, and transcripts. If no indexed match is found, it falls back to substring matching on titles, descriptions, and transcripts, including Chinese. Substring and timeout fallbacks cover at most the 300 newest episodes matching all filters. Returned fallback matches, and all timeout fallbacks, carry partial: true and searched_recent: 300; that number is a window limit, not the actual number scanned. An empty response can omit these markers after a successful full-text query; it still does not rule out older substring matches.

q       required · 2–100 characters
show    exact show id, or __untracked__ for untracked shows (search only)
person  exact participant name, without the "person:" prefix
lang    EN | ZH · original episode language, not desired output language
len     lt30 (<30) | 30to60 (30–59) | 60to120 (60–119) | gt120 (≥120)
time    24h | 7d | 30d  (exact release time; date fallback where unavailable)
asset, sector, focus  comma-separated tag IDs from the groups above

Examples

# English and Chinese search
curl "https://bidclub.ai/api/v1/search?q=OpenAI&lang=EN"
curl --get "https://bidclub.ai/api/v1/search" --data-urlencode "q=泡沫"

# combine transcript search filters
curl --get "https://bidclub.ai/api/v1/search" \
  --data-urlencode "q=AI" \
  --data-urlencode "person=Jensen Huang" \
  --data-urlencode "len=60to120" \
  --data-urlencode "time=30d"

# five newest metadata records from one show
curl "https://bidclub.ai/api/v1/episodes?show=iltb&limit=5"

# match either asset class, plus the selected sector and focus
curl "https://bidclub.ai/api/v1/episodes?asset=equities,vc_pe&sector=ai_software&focus=investing&limit=20"
curl "https://bidclub.ai/api/v1/search?q=AI&sector=semis,ai_software&focus=technical"

# next page of metadata; follow pagination.next_offset until it is null
curl "https://bidclub.ai/api/v1/episodes?limit=100&offset=100"

# full episode as JSON, then a terminal-friendly transcript
curl "https://bidclub.ai/api/v1/episodes/ai-selloff-gavin-baker"
curl "https://bidclub.ai/dl/ai-selloff-gavin-baker/transcript.txt"

Response fields

The collection returns episodes plus pagination {limit, offset, next_offset}; search returns q, count, results and optional partial / searched_recent markers; feed indexes return count and episodes. All episode surfaces include all three tag arrays and classification status/version. Collections sort by published_at descending (nulls last), then date descending and slug ascending. title is the full canonical publisher title; display_title is a nullable feed-only compression. Nullable source and translation fields return null when unavailable.

SHOW
  id, name, lang, hosts, tracked, position, sources

TAGS · on every episode list, detail, search result, and feed-index row
  tag_assets, tag_sectors, tag_focus, tagging_status, tagging_version

EPISODE LIST ITEM
  slug, show_id, title, display_title, title_orig, dek, lang, date, published_at, duration_min,
  source_url, source_label, rss_url, youtube_id, youtube_url,
  chips, provenance, tags above, shows { name }

EPISODE DETAIL
  list fields + thumbnail_url, title_alt, display_title_alt, dek_alt, lang_alt,
  tldr_md, digest_md, transcript_md, tldr_md_alt, digest_md_alt,
  shows { name, hosts }

SEARCH RESULT
  list fields minus provenance + title_alt, display_title_alt, dek_alt, thumbnail_url

FEED INDEX ROW
  search fields minus rss_url, youtube_url, shows
  + show (flattened show name)

lang describes the source-language editorial fields. lang_alt names the alternate-language title, dek, TL;DR, and digest on the full record. The catalog's lang filter selects source episodes; it does not select translated output. transcript_md always preserves the source language and has no alternate. chips contains only person:<name> entries. A slug remains stable when a title is corrected.

Download behavior

summary contains the TL;DR and digest; transcript contains the full transcript; full combines available sections. Markdown preserves structure, text removes most Markdown syntax, and PDF is typeset and cached on first request. A show route streams a ZIP with a README recording unavailable artifacts. ZIP assembly uses one database listing, subject to its row cap; use paginated JSON for complete exports of large shows. Large PDF archives can take minutes.

Both download routes accept ?lang=EN|ZH|orig (default orig). A translated summary requires both the translated TL;DR and digest; otherwise it falls back to the source edition. Transcripts always remain in their original language, including within full exports. Single-episode responses report X-BidClub-Language and X-BidClub-Language-Fallback; ZIPs record fallbacks in README.txt. Transcript-only Markdown is served inline and cached, including full.md when no summary exists; other downloads are attachments with private, no-store. Use the JSON API for automated bulk reads.

Caching and updates

Configured Cache-Control policies (seconds)

Shows and episode lists  public, s-maxage=300, stale-while-revalidate=600
Episode detail           public, s-maxage=60
Search                   public, s-maxage=60
All feed indexes         public, max-age=60, s-maxage=300, stale-while-revalidate=600
RSS feeds                public, max-age=60, s-maxage=300, stale-while-revalidate=600
Transcript-only Markdown public, s-maxage=300, stale-while-revalidate=86400
Other downloads          private, no-store

Published episodes can be corrected, translated, or reclassified. Publication hooks refresh site/data caches, but different routes may briefly reflect different revisions. Shared-cache directives can be consumed by the CDN rather than repeated in the browser's response headers. These policies are not a guaranteed end-to-end update deadline; avoid tight polling. JSON routes do not promise ETag or conditional 304 responses.

Limits and errors

The versioned episode collection accepts at most 100 rows per request; follow pagination.next_offset until it is null. The one-response catalog is deliberately much larger and is intended for occasional discovery or mirroring, not tight polling. JSON API errors use an error field: 400 for an invalid query, 404 for a missing episode, and 502 when the upstream is unavailable. Download routes use the corresponding HTTP status with a short text response.

Machine discovery

Agents and code generators can read /openapi.json for the public REST and download routes. For direct PostgREST queries, use the public schema below; database-root schema discovery is not available with the public key. Every /api/* response carries X-Robots-Tag: noindex — that governs search indexing of the JSON, not access to it.

GET /openapi.json
    OpenAPI 3.1 schema: every endpoint, parameter, and response shape

GET /llms.txt
    short discovery file for language models (llmstxt.org convention)

GET /llms-full.txt
    the same, with the complete API reference and live show registry inlined

Agents

An agent can reach the library three ways, in descending order of preference. The MCP server is the richest: it speaks Model Context Protocol over Streamable HTTP at /api/mcp, needs no key, and exposes six tools. It is rate limited to 30 requests per minute per IP because these calls bypass the CDN; for bulk reads use the cached REST endpoints above.

POST https://bidclub.ai/api/mcp

bidclub_list_shows        the show registry
bidclub_list_episodes     episode metadata, newest first
bidclub_search_episodes   full-text and Chinese substring search
bidclub_get_episode       one section, paged deterministically for long text
bidclub_feed_index        compact index; may truncate large results
bidclub_download_links    the /dl URL matrix, built without fetching

# Claude Code
claude mcp add --transport http bidclub https://bidclub.ai/api/mcp

# any client that speaks Streamable HTTP
{ "mcpServers": { "bidclub": { "url": "https://bidclub.ai/api/mcp" } } }

# stdio-only clients bridge through mcp-remote
npx -y mcp-remote https://bidclub.ai/api/mcp

MCP list, search, and feed-index tools accept the same comma-separated asset / sector / focus filters. source_lang filters original episode language; lang on bidclub_get_episode selects editorial output language. MCP feed-index has a response-size limit: if truncated is true, count is the full matching total but episodes contains only a subset. Use the REST feed index for a complete catalog.

Long sections page rather than truncate: when a result carries truncated: true, call bidclub_get_episode again with offset set to next_offset and concatenate content_md until next_offset is null. The window defaults to 20,000 characters for Latin text and 14,000 for Chinese, where one character costs roughly one token.

For an agent with no MCP support, the Claude Code skill teaches the same workflow over plain HTTP. It is one file and installs with one command. Shells and scripts can use the curl examples above.

mkdir -p ~/.claude/skills/bidclub \
  && curl -fsSL https://bidclub.ai/skill/SKILL.md \
       -o ~/.claude/skills/bidclub/SKILL.md

Feeds

RSS 2.0 feeds, no account and no API key. Each feed is published as two separate language editions rather than one mixed feed, and the site's language toggle decides which edition a link on the site points at. Global feeds cover tracked shows and carry the 30 newest episodes; a per-show feed exists for every show with a page — tracked or not — and carries 20. Per-show feed links live on the coverage page, one per row; the OPML files list tracked shows only.

GET /feeds/global.xml · /feeds/global.zh.xml
    all tracked shows, 30 newest episodes

GET /feeds/{show_id}.xml · /feeds/{show_id}.zh.xml
    one show, 20 newest episodes
    linked per row on /coverage

GET /opml · /opml.zh
    OPML 2.0 download: per-show feeds in ONE language, no global feed
    importing both languages would subscribe you to every show twice

Entry bodies are the dek plus the TL;DR and links back — the full digest, transcript, and downloads stay on the episode page. Item guids are stable, slug-based, and language-qualified: tag:bidclub.ai,2026:e:{slug}:en or :zh. They are tag: URIs rather than URLs, hence isPermaLink="false". A republished episode keeps its guid, so corrections refresh in place instead of arriving as a new unread item, and subscribing to both editions of one show never collapses them. Feeds carry Last-Modified and answer If-Modified-Since with 304. Their cache policy is listed above; reader refresh intervals also affect when an update appears. These are article feeds: no audio enclosures and no podcast namespace tags.

Direct database access

For explicit field selection, filtering, ordering, joins, and pagination, query the public database directly with the PostgREST query language. The key below is intentionally public; row-level security permits reads of published content and denies writes. PostgREST commonly caps one response at 1,000 rows, so direct-database clients must page with limit and offset (or Range headers) and should select only the fields they need.

SB="https://fexwajqmulkvqfsdxodo.supabase.co/rest/v1"
KEY="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImZleHdhanFtdWxrdnFmc2R4b2RvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODU5OTk5MDIsImV4cCI6MjEwMTU3NTkwMn0.vqZwHNNcpcFmPQ-Ca1Xlps9kjvsB0QMZOtdL4Nu4Rj4"

# page through compact rows; never assume an unbounded response
curl "$SB/episodes?select=slug,show_id,title,date,published_at&order=published_at.desc.nullslast,date.desc,slug.asc&limit=100&offset=0" \
  -H "apikey: $KEY"

# indexed full-text search across titles, summaries, and transcripts
curl "$SB/episodes?select=slug,title,date,published_at&fts=wfts(simple).NVIDIA&limit=50" \
  -H "apikey: $KEY"

# join each episode to its show
curl "$SB/episodes?select=slug,title,published_at,shows(name)&order=published_at.desc.nullslast,date.desc&limit=100" \
  -H "apikey: $KEY"

# any selected asset, AND the chosen sector (array overlap)
curl --get "$SB/episodes" -H "apikey: $KEY" \
  --data-urlencode "select=slug,title,tag_assets,tag_sectors,tag_focus,tagging_status" \
  --data-urlencode "tag_assets=ov.{equities,vc_pe}" \
  --data-urlencode "tag_sectors=ov.{ai_software}" \
  --data-urlencode "order=published_at.desc.nullslast,date.desc,slug.asc" \
  --data-urlencode "limit=100"

Public schema

shows
  id, name, lang, hosts, tracked, position, sources (jsonb), updated_at

episodes · identity and discovery
  slug, show_id → shows.id, title, display_title, title_orig, title_alt,
  display_title_alt, dek, dek_alt,
  lang, lang_alt, date, published_at, duration_min, thumbnail_url

episodes · source and provenance
  source_url, source_label, rss_url, youtube_id, youtube_url,
  chips (jsonb: ["person:<name>"]), provenance (jsonb: [[stage, tool], ...])

episodes · editorial content
  tldr_md, digest_md, transcript_md, tldr_md_alt, digest_md_alt

episodes · taxonomy
  tag_assets, tag_sectors, tag_focus (text[], 0–2 unique IDs each, primary first)
  tagging_status (pending | classified | failed | skipped)
  tagging_version (text, nullable)
  tagging_input_sha256 (text, nullable; direct database only)
  tagging_source_revision (bigint, ≥0; direct database only)

episodes · system
  status, updated_at, fts

published_at is the original episode release timestamp, not the time it was added to BidClub. updated_at is not a reliable cursor for every correction. tagging_source_revision only tracks source title / language / TL;DR / digest changes; tagging_input_sha256 binds a classification to its input and classifier settings. Neither field is a general change feed. Direct database arrays use PostgreSQL operators: ov matches any listed value; eq.{} finds an empty array. Unlike the REST filter shortcut, listing every ID in an ov filter still excludes empty arrays.

A link back to the BidClub episode and its original source is appreciated.