developer api
Everything the site does, from a script.
Search podcasts, list episodes, fetch transcripts as text, JSON, SRT or VTT with segment and word-level timestamps, queue new episodes for transcription, and ask AI Search to find a topic, name or keyword in any episode. One key, one credit balance, shared with the website.
Authentication
Accounts need a verified email, there's no way to get a spendable key without proving
control of a real inbox. Start at POST /api/v1/register/start, click the link
it emails you (or hit POST /api/v1/register/confirm with the token from that
link if you'd rather stay in a terminal), then pass the returned key as a bearer token.
curl -X POST https://podmenti.com/api/v1/register/start \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
# check your email, then either click the link or:
curl -X POST https://podmenti.com/api/v1/register/confirm \
-H "Content-Type: application/json" \
-d '{"token": "<from the emailed link>"}'
# every call after that:
curl https://podmenti.com/api/v1/me \
-H "Authorization: Bearer pm_YOUR_KEY" A new key starts with 0 credits. Discovery, episode lists and the sample transcript work straight away; anything that costs credits needs a plan or a one-time pack (1 episode for $1, 10 episodes for $5, 50 episodes for $15). Pack credits never expire and survive the monthly reset. Buy them on the website at podmenti.com/plans, or open the same Stripe Checkout from a script:
# key optional: without one, Stripe asks for an email and that becomes the account
curl -s -X POST https://podmenti.com/api/v1/checkout -H "$AUTH" \
-H "Content-Type: application/json" \
-d '{"pack": "pack10", "return": "/account"}'
# -> {"url": "https://checkout.stripe.com/…", "session_id": "cs_…"}: send the browser to url pack is one of single, pack10, pack50. The single is one specific episode, any length,
so it also needs episode: {guid, title, secs}.
After payment Stripe returns the browser to the site, which calls
POST /api/v1/checkout/claim with the session_id to apply the
credits.
Finding a show and its ids
Two ids run everything. A feed_id is the show's number in the open Podcast
Index, the directory of every public podcast. A transcript_id is the
episode's key: sixteen hex characters, sha1(episode guid) cut to sixteen,
identical for everyone, never changing. You never have to guess either: every discovery
call returns the ids and the URLs to call next, and none of them cost credits on any plan.
- Start from a name:
GET /api/v1/search?q=hubermanreturns shows withfeed_id,apple_idand anepisodes_url. - Start from a link: the same
?q=accepts an Apple Podcasts URL or an RSS feed URL. Or useGET /api/v1/shows/lookup?apple_id=…/?feed_url=…. Spotify links can't be resolved, Spotify hides the feed. - Start from what's popular:
GET /api/v1/charts?genre=comedy(Apple's daily charts, withapple_idand alookup_url) orGET /api/v1/trending?category=Technology(what's publishing right now across the index). - Then list episodes:
GET /api/v1/shows/{feed_id}/episodesgives every episode with itstranscript_id,transcript_status(ready, processing, none), audio URL, description, and the exact URLs to read or request it. - Check before you spend:
GET /api/v1/episodes/{transcript_id}tells you whether we hold it, its length, and whether your key already unlocked it (then it costs 0).
All of this is served from our own copy of the index, refreshed daily for shows and hourly for episode lists, so browsing is fast, never throttled by the index, and a bad minute there never becomes an error for you.
Endpoints
Two meters, and the last column says which one each call touches. Every call made
with a key counts as one request against your plan's daily allowance (Free 1,000, Plus 5,000, Pro 20,000, Business 50,000 a
day; X-RateLimit-Limit and X-RateLimit-Remaining on every
response, 429 when spent, reset at midnight UTC). Credits are charged on top
only where a price is shown, and only for work that costs us compute.
| endpoint | what it does | meter |
|---|---|---|
POST /api/v1/register/start | {"email"}: emails a one-time verification link. Same address
twice re-sends your existing key instead of creating a duplicate account. Creating
an account accepts the terms and privacy policy.
| no key |
POST /api/v1/register/confirm | {"token"}: the token from the emailed link. Free. A new
account starts with 0 credits.
| no key |
GET /api/v1/me |
Balance, plan and everything you've unlocked. credits_bought is how
much of credits came from packs (never expires); paying is
true with a plan or a pack.
| 1 request |
GET /api/v1/usage |
The fuller picture behind your account: this cycle's allowance
and what's left of it, requests used today, the credit ledger and recent unlocks.
Also carries credits_bought and paying, as on
/me.
| 1 request |
GET /api/v1/search?q= | Find shows by name, or resolve a pasted Apple Podcasts or RSS link. Returns feed_id, apple_id, categories, language and the URLs to call next. Free. | 1 request |
GET /api/v1/shows/lookup | ?url=, ?apple_id= or ?feed_url= to a feed_id. Free. | 1 request |
GET /api/v1/shows/{feed_id} | Show details: description, artwork, language, categories, feed and website URLs, Apple id, newest episode date. Free. | 1 request |
GET /api/v1/charts?genre= | Apple's US top chart, overall (100) or per genre (50), updated daily. Free. | 1 request |
GET /api/v1/trending?category= | Shows publishing right now across the whole index, optionally within a category; also lists the categories. Free. | 1 request |
GET /api/v1/shows/{feed_id}/episodes?max=100 |
Episodes with transcript_id and status: ready,
processing or none. Up to 1000, which covers a show's full
history except a handful of the most prolific, where PodcastIndex itself stops
tracking further back (response includes truncated: true when that's
the case).
| 1 request |
GET /api/v1/episodes/{transcript_id} | One episode by transcript id: title, show, date, length, word count, whether a transcript exists, and whether your key already unlocked it. Free. | 1 request |
GET /api/v1/transcripts/{transcript_id} |
The transcript, including the original audio_url, for an unlocked
episode: ?format=text, json,
json×tamps=1&words=1, srt,
vtt and timestamped txt. 402 until unlocked.
| 1 request |
POST /api/v1/unlock | {"transcript": "<id>"}: 1 credit, once per
episode of any length, then every format is yours forever.
| 1 credit+ 1 request |
POST /api/v1/requests |
Transcribe episodes we don't have yet: 1 credit per episode, any length,
up to 25 at a time, unlock included. Every episode runs on the fast lane, a rented GPU,
usually ready in a minute or two and promised within promise_seconds (at
least 5 minutes, longer for big batches). The old
fast field is still accepted and ignored; every job comes back with
fast: true. Pass secs from the episodes endpoint for an
accurate ETA. Returns a job with an ETA and poll URL; pass
webhook_url to be called when it lands. "dry_run": true quotes
the charge and the promise without spending. If you are short on credits, the 402
reads "This costs N credits, one per episode: ..." with the split. Episodes already in the library are unlocked on the spot for
1 each (nothing if this key had them already) and never re-queued, so the
response splits into already_transcribed (instant) and
queued, alongside credits_charged and
credits_remaining. Emails you when the queued ones land. Each new
episode's audio is checked first: ones the podcast host no longer serves come back in
unavailable and cost nothing (a 422 audio_unavailable if that
leaves nothing to do), and any episode that still can't be transcribed is refunded and
marked failed on the job.
| 1 credit / episodeany length, 1 per library episode, + 1 request |
GET /api/v1/jobs/{id} | Poll a transcription job: status, ETA, suggested poll interval, per-episode readiness with URLs (ready, queued, or failed and refunded). GET /api/v1/jobs lists recent ones. Free. | 1 request |
GET /api/v1/webhooks/secret | Your webhook signing secret (created on first call). POST rotates it. Free. | 1 request |
POST /api/v1/webhooks/inbox | A hosted https test receiver for 24 hours; GET /api/v1/webhooks/inbox/{id} shows what was delivered. Free. | 1 request |
POST /api/v1/ai-search/query | Search one episode for a topic, name or keyword and get verbatim quotes with timestamps. 1 credit a query, from a plan or a pack. If we don't hold that episode yet you get a 409 quoting the real total (1 to transcribe, plus 1 for the query, 2 in all; then query it as often as you like). | 1 credit+ 1 request |
POST /api/v1/key/rotate | Issues a new key and retires the current one immediately. Credits, plan and unlocks move across. | 1 request |
POST /api/v1/billing/portal | Returns a Stripe billing portal URL for the calling account: switch plan, cancel, update card. | 1 request |
POST /api/v1/checkout |
Starts a one-time purchase: {pack, episode?, return?}, see
above. Returns {url, session_id}; send a
browser to url. Key optional: without one, the email given to Stripe
becomes the account.
| key optional |
POST /api/v1/checkout/claim | {"session_id"}: applies a paid checkout if it hasn't been
already and says where to go next (pending until Stripe confirms).
| key optional |
Word-level timestamps
Our transcriber stores the start time of every single word, not just every subtitle line.
After unlocking, ?format=json×tamps=1&words=1 returns them:
{
"segments": [{
"start": 61.4, "end": 66.9,
"text": "And I'm Tim Houlihan. So we talk with researchers",
"words": [
{"w": "And", "s": 61.4}, {"w": "I'm", "s": 61.55},
{"w": "Tim", "s": 61.72}, {"w": "Houlihan.", "s": 61.94}, …
]
}, …]
} That powers karaoke-style highlighting, precise audio deep links, clip cutting and search that jumps to the exact second. SRT and VTT exports drop straight into video editors and players.
Shows using dynamic ad insertion re-splice their audio over time, so the file at
audio_url today can differ from the one a transcript was cut against.
Podmenti detects that change and re-aligns every timestamp automatically, so what the API
returns always matches the audio the host is serving now. How that
works.
AI Search
Instead of downloading a transcript and searching it yourself, ask a question and get back
exactly where it's answered. This is the same kind of extraction that runs Bookmenti's
book-mention pipeline at scale, pointed at whatever you ask: see
how it works for a real example. Give
POST /api/v1/ai-search/query a transcript_id and a
topic ("mentions of Acme Corp", a person's name, a product, a claim you want
fact-checked), and it returns verbatim quotes with timestamps:
the request
curl -s -X POST https://podmenti.com/api/v1/ai-search/query -H "$AUTH" \
-H "Content-Type: application/json" \
-d '{"transcript_id": "a7c18c255b33d645",
"topic": "books recommended or referenced by title, with the author if mentioned"}' the response, complete, exactly as the API returned it for the sample episode
{
"transcript_id": "a7c18c255b33d645",
"topic": "books recommended or referenced by title, with the author if mentioned",
"found": true,
"summary": "The podcast transcript references several books by title and author, including works by Elizabeth Gilbert, Anne Lamott, Jerry Seinfeld, Mary Carr, Brandon Sanderson, and Seth Godin.",
"mentions": [
{
"quote": "Elizabeth Gilbert is the author of Eat Pray Love Big magic. And city of girls.",
"context": "Tim Ferriss introduces Elizabeth Gilbert by mentioning several of her books.",
"timestamp_seconds": 114.6,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=114"
},
{
"quote": "Anne Lamott is the author of twenty books. Including Bird by bird. Operating instructions. And help. Thanks. Wow.",
"context": "Tim Ferriss introduces Anne Lamott, listing several of her authored books.",
"timestamp_seconds": 387.1,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=387"
},
{
"quote": "I'm gonna ask a question that no doubt you've been asked before, but I have to ask What do you think it is about bird by bird? that affected so many people so deeply.",
"context": "Tim Ferriss asks Anne Lamott about the impact of her book Bird by Bird.",
"timestamp_seconds": 402.1,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=402"
},
{
"quote": "He he's uh Neil Allen. He wrote Shapes of Truth.",
"context": "Anne Lamott mentions her husband Neil Allen's book Shapes of Truth while discussing inner critic work.",
"timestamp_seconds": 654.4,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=654"
},
{
"quote": "Joy Carol Oates has written more than seventy books, including Blonde And we were the Mulvanies. and won the National Book Award for her novel Them.",
"context": "Tim Ferriss introduces Joyce Carol Oates and references several of her books.",
"timestamp_seconds": 786.7,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=786"
},
{
"quote": "And wrote Is this anything?",
"context": "Tim Ferriss mentions Jerry Seinfeld's book Is this anything? during his introduction.",
"timestamp_seconds": null,
"listen_url": null
},
{
"quote": "I picked up this book uh by Bill Phillips called Body for Life.",
"context": "Jerry Seinfeld talks about using Bill Phillips's book Body for Life to help him get in shape.",
"timestamp_seconds": 1254.8,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=1254"
},
{
"quote": "Mary Carr wrote the best selling memoirs. The Liers Club? Cherry and Lit And her book on writing. The Art of Memoir.",
"context": "Tim Ferriss introduces Mary Carr and lists her memoirs and her book on writing.",
"timestamp_seconds": 1589.3,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=1589"
},
{
"quote": "Brandon Sanderson created the Mistborn and Stormlight archive series.",
"context": "Tim Ferriss introduces Brandon Sanderson, referencing his major series.",
"timestamp_seconds": 2110.6,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=2110"
},
{
"quote": "I read uh On Writing by Stephen King and How to Write Sci Fi and Fantasy by Orson Scottkard.",
"context": "Brandon Sanderson discusses reading books by Stephen King and Orson Scott Card while studying writing before breaking in.",
"timestamp_seconds": 2304.3,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=2304"
},
{
"quote": "Seth Godin has written twenty-one bestsellers published in forty languages, and Including Purple Cow? Lynchpin. And the practice.",
"context": "Tim Ferriss introduces Seth Godin by listing several of his best-selling books.",
"timestamp_seconds": 2707.2,
"listen_url": "https://podmenti.com/t/a7c18c255b33d645#t=2707"
},
{
"quote": "First twenty years ago. I wrote a book called Permission Marketing.",
"context": "Seth Godin discusses writing his early book Permission Marketing.",
"timestamp_seconds": null,
"listen_url": null
},
{
"quote": "And thanks to Malcolm. He sent me he was unknown. He sent me this new book called The Tipping Point.",
"context": "Seth Godin recounts receiving Malcolm Gladwell's book The Tipping Point.",
"timestamp_seconds": null,
"listen_url": null
},
{
"quote": "And 10 years later, the same 2010 you're saying. I wrote a book called Lynchpin.",
"context": "Seth Godin talks about writing his book Lynchpin.",
"timestamp_seconds": null,
"listen_url": null
}
],
"credits": 8
}
The episode has to exist first: if transcript_status isn't ready,
queue it with POST /api/v1/requests (1 credit, any length), and we'll
email you when it lands. Each query is 1 credit, from a plan or a pack, and
only charges on success: a failed query is refunded automatically. Want this running
continuously across a show, with a webhook when something new turns up? Write to
contact@podmenti.com, that's next.
Jobs: polling, webhooks and the ETA
Transcription takes minutes, so POST /api/v1/requests does not make you
wait. Episodes we already hold come back unlocked immediately; the rest become a
job. The job carries an honest estimate: every episode runs on the GPU fast lane
and we measure its real speed, so estimated_ready_at is arithmetic, not a guess,
and promise_seconds is the time we promise it within. Then either poll it or
let us call you.
start a job (webhook optional)
curl -s -X POST https://podmenti.com/api/v1/requests -H "$AUTH" \
-H "Content-Type: application/json" \
-d '{"episodes": [{"guid": "…", "url": "https://…/episode.mp3", "title": "…", "secs": 3600}],
"webhook_url": "https://example.com/hooks/podmenti"}'
# one credit per new episode, any length, on the GPU fast lane response
{
"request_id": "9f1c2b7e-3d44-4c1a-9a6e-2f0b5c7d8e91",
"job": {
"id": "9f1c2b7e-3d44-4c1a-9a6e-2f0b5c7d8e91",
"status": "queued",
"fast": true,
"promise_seconds": 300,
"estimated_ready_at": "2026-09-02T07:41:00.000Z",
"estimated_seconds": 95,
"queue_ahead_episodes": 0,
"poll_url": "https://podmenti.com/api/v1/jobs/9f1c2b7e-3d44-4c1a-9a6e-2f0b5c7d8e91",
"poll_after_seconds": 15,
"webhook_url": "https://example.com/hooks/podmenti",
"episodes": [
{
"transcript_id": "3b9e1f0c7a2d4e58",
"title": "Episode title",
"status": "queued",
"charged": 1
}
]
},
"already_transcribed": [],
"queued": 1,
"credits_charged": 1,
"credits_remaining": 9,
"delivery": "transcribing now, estimated ready 2026-09-02T07:41:00.000Z, usually within 5 minutes; poll the job, or we call your webhook and email you when it lands",
"notify": "you@example.com"
} poll it
# come back after poll_after_seconds
curl -s https://podmenti.com/api/v1/jobs/9f1c2b7e-3d44-4c1a-9a6e-2f0b5c7d8e91 -H "$AUTH" GET /api/v1/jobs/{id} returns the same job object with a live
status (queued, processing, done), a fresh poll_after_seconds
(a quarter of the remaining estimate, between 10 and 30 seconds), and per-episode
status with the transcript and page URLs once each one is ready. GET
/api/v1/jobs lists your recent jobs.
or receive this at your webhook when every episode has settled
{
"event": "job.done",
"job": {
"id": "9f1c2b7e-3d44-4c1a-9a6e-2f0b5c7d8e91",
"status": "done",
"poll_url": "https://podmenti.com/api/v1/jobs/9f1c2b7e-3d44-4c1a-9a6e-2f0b5c7d8e91",
"episodes": [
{
"transcript_id": "3b9e1f0c7a2d4e58",
"title": "Episode title",
"status": "ready",
"url": "https://podmenti.com/t/3b9e1f0c7a2d4e58",
"transcript_url": "https://podmenti.com/api/v1/transcripts/3b9e1f0c7a2d4e58"
}
],
"done": 1,
"total": 1
},
"sent_at": "2026-09-02T07:38:12+00:00"
}
The webhook is a POST with X-Podmenti-Event: job.done,
X-Podmenti-Job and X-Podmenti-Signature headers, delivered from
our transcription server the moment the transcripts are readable. https only. A
non-2xx answer or a timeout is retried with backoff: after 1, 5, 15, 60 and 240 minutes,
then we give up and the job stays pollable. Deliveries are signed like Stripe's:
t=<unix>,v1=<hex HMAC-SHA256(secret, t + "." + raw body)>, with the
secret from GET /api/v1/webhooks/secret (created on first call,
POST to rotate). Verify over the raw body, compare in constant time, and
reject a t older than five minutes.
Nothing to receive it with yet? POST /api/v1/webhooks/inbox gives you a hosted
https URL for 24 hours; pass it as webhook_url, then read
GET /api/v1/webhooks/inbox/{id} to see exactly what we sent, headers
and all. The same inbox is what our own smoke test uses.
A full example
# find the show (by name, or paste an Apple / RSS link)
curl -s "https://podmenti.com/api/v1/search?q=huberman" -H "$AUTH"
# -> shows[0].feed_id = 1365758, plus episodes_url
# list episodes, grab a transcript_id with transcript_status "ready"
curl -s "https://podmenti.com/api/v1/shows/1365758/episodes" -H "$AUTH"
# what would it cost me? (0 if already unlocked)
curl -s "https://podmenti.com/api/v1/episodes/$ID" -H "$AUTH"
# unlock once (1 credit), then read and export in every format
curl -s -X POST https://podmenti.com/api/v1/unlock -H "$AUTH" \
-H "Content-Type: application/json" -d '{"transcript": "'$ID'"}'
curl -s "https://podmenti.com/api/v1/transcripts/$ID?format=text" -H "$AUTH"
curl -sO "https://podmenti.com/api/v1/transcripts/$ID?format=srt" -H "$AUTH" Pricing and limits
- One credit is one episode, any length. An episode we already hold is 1 credit to unlock, every format included. One we don't is 1 to transcribe, unlock included. Nothing else moves a price, and a failed episode is refunded in full automatically.
- Unmetered on every plan: search, episode lists and transcript status.
- AI Search: 1 credit per query.
- New keys start with 0 credits. Credits come from a plan or a one-time pack.
- Plans: 40 episodes/mo for $9, 150 episodes/mo for $29, 600 episodes/mo for $99, at /plans. Plan credits reset every billing cycle, no rollover. Plans are the cheapest per episode.
- Packs: 1 episode for $1, 10 episodes for $5, 50 episodes for $15. A pack of N episodes is N credits; the single is one specific episode, any length. Pack credits never expire and survive the monthly reset. Buy at /plans or through
POST /api/v1/checkout. - Speed: every episode runs on the GPU fast lane, ready in minutes whatever the length, at the same 1 credit. The measured median is on /plans and in
faston /api/queue; the promise is on the job. - Requests: every call with a key counts one against the plan's daily allowance, Free 1,000, Plus 5,000, Pro 20,000, Business 50,000 a day.
X-RateLimit-LimitandX-RateLimit-Remainingride on every response;GET /api/v1/usagereportsrequests_todayandrequest_limit. Resets at midnight UTC. - Errors are JSON:
{"error": {"code", "message"}}. 402 means credits are needed, 429 means today's requests are spent.
OpenAPI and llms.txt
The whole API is described in /openapi.json (OpenAPI 3.1), which any client generator or AI agent can read directly, and summarised for language models in /llms.txt. Both quote the same prices as this page, because they are generated from the same source.
Building something bigger, need higher limits, or want an MCP connector for your AI assistant? Write to contact@podmenti.com.