{"openapi":"3.1.0","info":{"title":"BitePods API","version":"1.0.0","summary":"Clip the exact moment of a podcast episode, and share it as a link.","description":"A clip is a **pointer**: the publisher's own URL plus an in and an out point. Nothing is stored or re-hosted, so a clip fits inside its own link and there is no clip database to read from.\n\nNo key is needed. Times are seconds from the start of the episode.\n\nThe same operations are available to agents over MCP at `/mcp` (Streamable HTTP), and as prose at `/llms-full.txt`."},"servers":[{"url":"https://bitepods.com"}],"security":[],"paths":{"/api/v1/shows/search":{"get":{"operationId":"search_shows","summary":"Find podcasts by name","description":"Searches the public podcast directory by show name. Use it when you have a name rather than a link. Each hit carries the show's RSS feed as `sourceUrl` — hand that to `list_episodes`. It searches shows, never episodes: there is no index of episode titles to search, so finding one episode means listing a show's episodes and matching the title yourself. Shows Apple rates explicit are left out.\n\nUsually followed by: list_episodes with a show's sourceUrl.","parameters":[{"name":"q","in":"query","required":true,"description":"The show's name, or enough of it to be distinctive. Two characters minimum.","schema":{"type":"string","examples":["hard fork"],"description":"The show's name, or enough of it to be distinctive. Two characters minimum."}},{"name":"limit","in":"query","required":false,"description":"How many shows to return, at most. Clamped to 25.","schema":{"type":"integer","minimum":1,"maximum":25,"default":12,"description":"How many shows to return, at most. Clamped to 25."}}],"responses":{"200":{"description":"`{ shows }` — id, name, author, artwork, feed, and the `sourceUrl` to list episodes from.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShowResults"}}}},"400":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/shows/top":{"get":{"operationId":"top_shows","summary":"The podcast charts","description":"The most-subscribed shows in a country, ranked. Useful as a starting point when nothing specific was named, and as a way to check that a show is the well-known one rather than a similarly named feed. Country defaults to the one the request came from.\n\nUsually followed by: list_episodes with a show's sourceUrl.","parameters":[{"name":"country","in":"query","required":false,"description":"Two-letter ISO country code (`us`, `gb`, `de`). Defaults to the country the request arrives from, and to `us` when that is unknown.","schema":{"type":"string","examples":["us"],"description":"Two-letter ISO country code (`us`, `gb`, `de`). Defaults to the country the request arrives from, and to `us` when that is unknown."}},{"name":"limit","in":"query","required":false,"description":"How many shows to return, at most. Clamped to 100.","schema":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"How many shows to return, at most. Clamped to 100."}}],"responses":{"200":{"description":"`{ country, shows }`, each show carrying its 1-based `rank`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ShowResults"}}}},"400":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/episodes":{"get":{"operationId":"list_episodes","summary":"List a show's episodes","description":"Episodes of a show, newest first, from its RSS feed or its Apple Podcasts page. Feeds are read up to 60 items — that is the cap, not a page you can walk past. Each episode carries a `clipUrl`: pass it back as `url` to `create_clip` or `get_transcript` and the right episode is clipped, whatever the show's pages look like. Errors with `not_a_show` if the URL is one episode rather than a show.\n\nAn episode from a feed may also carry `chapters` — the publisher's own timestamp index, read out of the show notes, as `{ startSeconds, title }` in the order they happen. That is a human being naming the parts of their own episode, so it is the cheapest way to find a moment worth clipping: pick a chapter, pass its `startSeconds` to `create_clip` as `start`, and the next chapter's `startSeconds` as `end`. Roughly two shows in five publish one, and a show that doesn't simply has no `chapters` field.\n\nUsually followed by: An episode's own `chapters` are the quickest way to a moment; get_transcript reads the words where a show publishes one. Then create_clip.","parameters":[{"name":"url","in":"query","required":true,"description":"A show: its RSS feed or Apple Podcasts page.","schema":{"type":"string","examples":["https://feeds.simplecast.com/l2i9YnTd"],"description":"A show: its RSS feed or Apple Podcasts page."}},{"name":"limit","in":"query","required":false,"description":"How many episodes to return, at most. Clamped to 60.","schema":{"type":"integer","minimum":1,"maximum":60,"default":50,"description":"How many episodes to return, at most. Clamped to 60."}},{"name":"offset","in":"query","required":false,"description":"Skip this many episodes first, to page through a long feed.","schema":{"type":"integer","minimum":0,"default":0,"description":"Skip this many episodes first, to page through a long feed."}}],"responses":{"200":{"description":"`{ show, episodes, total, offset, limit, hasMore }`. An episode may carry `chapters`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EpisodeList"}}}},"400":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/resolve":{"get":{"operationId":"resolve_url","summary":"Work out what a URL points at","description":"Turns any pasted link into something clippable, and says which of three things it is: `episode` (one playable thing, ready for create_clip), `show` (a feed or channel — a preview of its episodes, with list_episodes for the rest), or `unresolved` (nothing playable was found, and why). Answering `unresolved` is a real answer, not a failure: clipping the wrong audio confidently is worse. Spotify links resolve to the same episode in the show's own feed, because nothing else can play Spotify.\n\nUsually followed by: create_clip for an episode, list_episodes for a show.","parameters":[{"name":"url","in":"query","required":true,"description":"Anything that points at an episode: an Apple Podcasts or Spotify episode link, an episode's own web page, an RSS feed, or a direct .mp3/.m4a file.","schema":{"type":"string","examples":["https://lexfridman.com/anthony-kaldellis/"],"description":"Anything that points at an episode: an Apple Podcasts or Spotify episode link, an episode's own web page, an RSS feed, or a direct .mp3/.m4a file."}}],"responses":{"200":{"description":"`{ type: 'episode' | 'show' | 'unresolved', … }` — always 200; read `type`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Resolution"}}}},"400":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/transcript":{"get":{"operationId":"get_transcript","summary":"Read the publisher's transcript, timed","description":"The episode's own transcript with absolute timestamps in seconds, so a line's `start` is a number you can pass straight to create_clip. This is how you find the moment worth clipping: read the transcript, choose the span, clip it.\n\nThese are the publisher's transcripts (Podcasting 2.0 `<podcast:transcript>`), and only about one show in six ships one. There is no speech-to-text here — deliberately, and it is not coming — so an episode without one answers `available: false` with a reason rather than an approximation. Cue-timed, not word-timed: a long cue is split into readable lines and `estimated: true` says the split points are interpolated between two known times.\n\nUsually followed by: create_clip with a line's start and end.","parameters":[{"name":"url","in":"query","required":true,"description":"The episode. An episode page, a direct audio file, or a `clipUrl` from list_episodes.","schema":{"type":"string","examples":["https://lexfridman.com/anthony-kaldellis/"],"description":"The episode. An episode page, a direct audio file, or a `clipUrl` from list_episodes."}},{"name":"showUrl","in":"query","required":false,"description":"The show the episode came out of (its feed). Pass the episode's `showUrl` from list_episodes: an episode whose `clipUrl` is its audio file finds its transcript through the feed.","schema":{"type":"string","description":"The show the episode came out of (its feed). Pass the episode's `showUrl` from list_episodes: an episode whose `clipUrl` is its audio file finds its transcript through the feed."}},{"name":"start","in":"query","required":false,"description":"Only lines that overlap from this many seconds into the episode.","schema":{"type":"number","minimum":0,"default":0,"description":"Only lines that overlap from this many seconds into the episode."}},{"name":"end","in":"query","required":false,"description":"Only lines that overlap up to this many seconds. 0 means to the end of the episode.","schema":{"type":"number","minimum":0,"default":0,"description":"Only lines that overlap up to this many seconds. 0 means to the end of the episode."}},{"name":"limit","in":"query","required":false,"description":"How many lines to return, at most. Clamped to 2000.","schema":{"type":"integer","minimum":1,"maximum":2000,"default":400,"description":"How many lines to return, at most. Clamped to 2000."}}],"responses":{"200":{"description":"`{ available, lines, lineCount, truncated, estimated, episode, … }`; `available: false` carries `reason`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Transcript"}}}},"400":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/clips":{"post":{"operationId":"create_clip","summary":"Make a shareable clip","description":"Marks a span of an episode and hands back the links that play it. Nothing is downloaded, transcoded or re-hosted: a clip is a pointer — the publisher's own URL plus an in and an out point — so the whole clip fits in its link and this call cannot fail for reasons of storage. The episode's name and cover are resolved and returned, so you can check you clipped what you meant to before sharing the link.\n\nTimes are seconds from the start of the episode, decimals allowed.\n\nUsually followed by: share links.short (or links.page); links.mp3 downloads the audio.","parameters":[],"responses":{"200":{"description":"`{ clip, links: { page, short, sourceAtMoment, mp3 }, shareText }`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Clip"}}}},"400":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"requestBody":{"required":true,"description":"Arguments, as a JSON object.","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","examples":["https://lexfridman.com/anthony-kaldellis/"],"description":"The episode to clip. Use the `clipUrl` from list_episodes or resolve_url when you have one."},"start":{"type":"number","minimum":0,"examples":[754.3],"description":"Where the clip starts, in seconds from the start of the episode."},"end":{"type":"number","minimum":0,"examples":[784.5],"description":"Where it ends, in seconds. Must be at least half a second after `start`."},"title":{"type":"string","examples":["The bit where he admits the model was guessing"],"description":"A title of your own for this clip, shown in place of the episode's on the clip's card, its page and the link's unfurl. The episode's name says which episode; this says what the clip is, which is what somebody scrolling past the link is deciding on. Trimmed to one line and cut at 50 characters, which is what the card holds without clamping on a phone. Leave it out and the episode's own name is used."},"showUrl":{"type":"string","description":"The show this episode came out of (its feed). Pass the `showUrl` a resolve or an episode list gave you: it is what lets the clip page name the episode when its own page won't say, and what keeps a clip playable when the page moves."},"audioUrl":{"type":"string","description":"The episode's audio file, when `url` is a page rather than the file. Pass the `audioUrl` from a resolve or an episode list: the clip then plays without waiting on the page to be read again."},"shorten":{"type":"boolean","default":true,"description":"Also register the `/x/<id>` short link, which is the one thing this service stores. Set false to get only the self-contained long link and write nothing."}},"required":["url","start","end"],"additionalProperties":false}}}}}},"/api/v1/clips/{id}":{"get":{"operationId":"get_clip","summary":"Read a clip back by its short id","description":"What a short id stands for: the episode, the two times, the links, and how many times the clip has been played. The id is the `/x/<id>` part of a short link. Play counts are approximate by design — a per-clip counter with no atomic increment behind it, not an audit trail.","parameters":[{"name":"id","in":"path","required":true,"description":"The short id, 8-32 characters, as it appears in `/x/<id>`.","schema":{"type":"string","examples":["k4m7pq2r"],"description":"The short id, 8-32 characters, as it appears in `/x/<id>`."}}],"responses":{"200":{"description":"`{ clip, links, plays, shareText }`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Clip"}}}},"400":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"A refusal with a code to branch on.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/clip.mp3":{"get":{"operationId":"download_clip_mp3","summary":"The clip as an MP3 file","description":"The publisher's own MP3 frames between two times, cut on frame boundaries, faded in and out, and tagged with the episode. Nothing is stored and nothing is re-encoded; the bytes pass through once. MP3 sources only — anything else answers 415 rather than a file that isn't the episode. At most 10 minutes per clip. `create_clip` returns this URL, already built, as `links.mp3`.","parameters":[{"name":"a","in":"query","required":true,"description":"The episode's audio file URL.","schema":{"type":"string"}},{"name":"s","in":"query","required":true,"description":"Start, in seconds.","schema":{"type":"number"}},{"name":"e","in":"query","required":true,"description":"End, in seconds.","schema":{"type":"number"}},{"name":"t","in":"query","required":false,"description":"Episode title, written into the file's tags.","schema":{"type":"string"}},{"name":"p","in":"query","required":false,"description":"Show name, written into the file's tags.","schema":{"type":"string"}}],"responses":{"200":{"description":"The clip, as an MP3.","content":{"audio/mpeg":{"schema":{"type":"string","format":"binary"}}}},"400":{"description":"Missing or impossible times."},"415":{"description":"The source isn't an MP3, so it can't be cut without re-encoding it."},"502":{"description":"The publisher's file couldn't be read."}}}}},"components":{"schemas":{"Show":{"type":"object","description":"A show in the public podcast directory. Not playable on its own — resolve `sourceUrl` into episodes.","properties":{"id":{"type":"string","description":"Stable within one answer; Apple's collection id when Apple answered."},"name":{"type":"string"},"author":{"type":"string","description":"The publisher, as the directory names it."},"artwork":{"type":"string","format":"uri"},"feedUrl":{"type":"string","format":"uri","description":"The show's RSS feed, when the directory names one."},"pageUrl":{"type":"string","format":"uri","description":"The show's Apple Podcasts page."},"rank":{"type":"integer","description":"1-based chart position, on chart answers only."},"sourceUrl":{"type":"string","format":"uri","description":"What to pass to list_episodes: the feed, or the Apple page as a fallback."}},"required":["id","name"]},"Episode":{"type":"object","properties":{"title":{"type":"string"},"kind":{"type":"string","enum":["audio"],"description":"How it plays back."},"clipUrl":{"type":"string","format":"uri","description":"Pass this back as `url` to create_clip or get_transcript."},"audioUrl":{"type":"string","format":"uri","description":"The publisher's audio file, for an audio episode."},"showUrl":{"type":"string","format":"uri","description":"The show this came out of. Pass it to create_clip alongside `url`."},"pageUrl":{"type":"string","format":"uri","description":"The episode's own web page, when it has one of its own."},"publishedAt":{"type":"integer","description":"Unix seconds, when the source gave a date."},"durationSeconds":{"type":"number"},"artwork":{"type":"string","format":"uri"},"transcriptUrl":{"type":"string","format":"uri"},"hasTranscript":{"type":"boolean","description":"Whether get_transcript will have anything to read."},"chapters":{"type":"array","description":"The publisher's own chapter marks, read from the episode notes. Absent when the show doesn't publish an index.","items":{"$ref":"#/components/schemas/Chapter"}}},"required":["title","kind","clipUrl","hasTranscript"]},"Chapter":{"type":"object","description":"A moment the publisher named, from their own timestamp index.","properties":{"startSeconds":{"type":"number","description":"Seconds from the start of the episode. Pass it to create_clip as `start`."},"title":{"type":"string","description":"The publisher's own words for this stretch."}},"required":["startSeconds","title"]},"ResolvedEpisode":{"type":"object","description":"One playable episode. Nearly an `Episode`, but `sourceUrl` is the URL you passed in, unchanged — and a page that names nothing still resolves, so `title` can be absent.","properties":{"kind":{"type":"string","enum":["audio"]},"sourceUrl":{"type":"string","format":"uri","description":"Exactly what was passed in."},"clipUrl":{"type":"string","format":"uri","description":"Pass this back as `url` to create_clip or get_transcript."},"audioUrl":{"type":"string","format":"uri"},"showUrl":{"type":"string","format":"uri"},"title":{"type":"string"},"show":{"type":"string"},"artwork":{"type":"string","format":"uri"},"durationSeconds":{"type":"number"},"transcriptUrl":{"type":"string","format":"uri"},"hasTranscript":{"type":"boolean"}},"required":["kind","sourceUrl","clipUrl","hasTranscript"]},"ShowResults":{"type":"object","properties":{"shows":{"type":"array","items":{"$ref":"#/components/schemas/Show"}},"country":{"type":"string","description":"Chart answers only: the country the chart is for."}},"required":["shows"]},"EpisodeList":{"type":"object","properties":{"show":{"type":"object","properties":{"name":{"type":"string"},"author":{"type":"string"},"artwork":{"type":"string","format":"uri"},"sourceUrl":{"type":"string","format":"uri"}}},"episodes":{"type":"array","items":{"$ref":"#/components/schemas/Episode"}},"total":{"type":"integer","description":"Episodes the source listed, up to the 60-item cap."},"offset":{"type":"integer"},"limit":{"type":"integer"},"hasMore":{"type":"boolean"}},"required":["show","episodes","total","offset","limit","hasMore"]},"Resolution":{"description":"Read `type` first. All three are 200s: an unresolvable link is an answer, not a failure.","oneOf":[{"type":"object","title":"episode","properties":{"type":{"const":"episode"},"episode":{"$ref":"#/components/schemas/ResolvedEpisode"}},"required":["type","episode"]},{"type":"object","title":"show","properties":{"type":{"const":"show"},"show":{"type":"object"},"episodeCount":{"type":"integer"},"episodes":{"type":"array","items":{"$ref":"#/components/schemas/Episode"},"description":"The first few; list_episodes returns the rest."},"more":{"type":"string"}},"required":["type","show","episodeCount","episodes"]},{"type":"object","title":"unresolved","properties":{"type":{"const":"unresolved"},"url":{"type":"string"},"message":{"type":"string","description":"Why nothing playable was found."}},"required":["type","url","message"]}]},"Transcript":{"type":"object","description":"The publisher's own transcript, timed in seconds from the start of the episode.","properties":{"available":{"type":"boolean"},"reason":{"type":"string","description":"Why there are no lines, when `available` is false."},"transcriptUrl":{"type":"string","format":"uri"},"episode":{"type":"object","properties":{"title":{"type":"string"},"show":{"type":"string"},"kind":{"type":"string"},"clipUrl":{"type":"string"},"durationSeconds":{"type":"number"}}},"window":{"type":"object","properties":{"start":{"type":"number"},"end":{"type":["number","null"]}}},"estimated":{"type":"boolean","description":"True when long cues were split, so the split times are interpolated."},"lineCount":{"type":"integer"},"totalLines":{"type":"integer"},"truncated":{"type":"boolean","description":"True when `limit` cut the answer; narrow the window or raise it."},"lines":{"type":"array","items":{"type":"object","properties":{"start":{"type":"number","description":"Seconds from the start of the episode — pass it to create_clip as-is."},"end":{"type":"number"},"text":{"type":"string"},"speaker":{"type":"string","description":"When the transcript says who's talking."}},"required":["start","end","text"]}}},"required":["available","episode"]},"Clip":{"type":"object","properties":{"shortId":{"type":"string","description":"The id in `/x/<id>`, when one was registered."},"shortened":{"type":"boolean"},"note":{"type":"string"},"plays":{"type":"integer","description":"Approximate: a per-clip counter, not an audit trail."},"clip":{"type":"object","properties":{"kind":{"type":"string","enum":["audio"]},"sourceUrl":{"type":"string","format":"uri","description":"Exactly what was passed in. Never rewritten — that's the promise."},"audioUrl":{"type":"string","format":"uri"},"showUrl":{"type":"string","format":"uri"},"start":{"type":"number"},"end":{"type":"number"},"lengthSeconds":{"type":"number"},"title":{"type":"string","description":"The episode, as resolved. Check it before you share the link."},"headline":{"type":"string","description":"The title passed as `title`, cleaned and capped — what the clip is called wherever it appears. Absent when none was given, in which case the episode's own title is used."},"show":{"type":"string"},"artwork":{"type":"string","format":"uri"},"durationSeconds":{"type":"number"}},"required":["kind","sourceUrl","start","end","lengthSeconds"]},"links":{"type":"object","properties":{"page":{"type":"string","format":"uri","description":"The clip, in a link that contains the whole clip."},"short":{"type":"string","format":"uri"},"sourceAtMoment":{"type":"string","format":"uri","description":"The publisher's own URL at that moment — no BitePods in the middle."},"mp3":{"type":"string","format":"uri","description":"The clip as a file. Audio only, 10 minutes at most."}},"required":["page","sourceAtMoment"]},"shareText":{"type":"string"}},"required":["clip","links","shareText"]},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","description":"The stable part. Branch on this, not on the wording."},"message":{"type":"string"},"hint":{"type":"string","description":"The call to make instead."}},"required":["code","message"]},"docs":{"type":"string","format":"uri"}},"required":["error"]}}}}