Search four million podcast feeds and get back a playable audio URL — and, when the feed publishes one, a transcript you can read instead. $0.002 per query, in USDC, on Base or Solana.
| Route | Price | What lands in the 200 body |
|---|---|---|
GET /search?q=… | $0.002 | Matching shows — title, author, description, feed URL, artwork, categories, episode count, language, Podcasting 2.0 value block — each with its newest episode, including the enclosure (audio) URL and transcript files |
GET /episode/:id | $0.002 | The full record for one episode: audio URL, byte length, duration, episode/season numbers, artwork, transcripts, GUID, feed context |
GET / · /health · /skill.md · /.well-known/x402 · /openapi.json | free | Discovery, and which data source this deployment is serving |
base-sepolia / baseexact · EIP-3009 transferWithAuthorization0x40252CFDF8B20Ed757D61ff157719F33Ec332402solana / solana-devnetexact · SPL transferChecked, fee sponsoredWwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwWBoth rails appear in the accepts array of every 402. Settlement runs through
the x402 facilitator, and the 200 carries an
X-PAYMENT-RESPONSE receipt naming the rail and the transaction.
# 1. run it — .env.example already has working defaults, no signup needed
npm install && npm run dev # http://localhost:4028
# 2. search without paying → the dual-rail challenge
curl -s 'localhost:4028/search?q=linux' | jq '.accepts[] | {network, payTo, maxAmountRequired}'
# { "network": "base-sepolia", "payTo": "0x40252CF…2402", "maxAmountRequired": "2000" }
# { "network": "solana", "payTo": "WwwuGbqH…T3WwW", "maxAmountRequired": "2000" }
# 3. pay → shows, each with its newest episode and audio URL
const pay = wrapFetchWithPayment(fetch, wallet);
const res = await pay(`${BASE}/search?q=podcasting%202.0`);
const { shows } = await res.json();
shows[0].latestEpisode.enclosureUrl
// "https://op3.dev/e/mp3s.nashownotes.com/PC20-180.mp3"
shows[0].latestEpisode.transcripts[0].url
// "https://mp3s.nashownotes.com/PC20-180.srt" ← read this, don't transcribe the MP3
Most podcast APIs give you titles and descriptions. This one gives you enclosureUrl — the actual MP3 — plus transcripts[], the Podcasting 2.0 transcript files a feed publishes.
That is the difference between “here are some podcasts about X” and a pipeline: search → audio URL → download, or better, search → transcript URL → read. An hour-long episode is a 78 MB download and several minutes of Whisper; its .srt is a few hundred kilobytes and one HTTP GET. When transcripts is non-empty, take it.
The upstream is the Podcast Index, the open catalogue behind Podcasting 2.0. Its keys are free and instant — but they are still keys, so this service is env-gated and says which mode it is in.
| credentials | behaviour | source field |
|---|---|---|
PODCAST_INDEX_KEY + PODCAST_INDEX_SECRET set | every call hits the live API at request time | "podcastindex-live" |
| unset | deterministic samples, so the demo runs with zero signups | "fixture" |
Fixtures are labelled, never hidden: every paid response carries source, GET / and GET /health report the mode, and the service card lists the fixture episode ids so /episode/:id works without keys. The samples are three real shows with one episode each, filtered by your query, so the shapes match live mode exactly. Never present a fixture response as a real search result.
Podcast search is spiky and occasional — an agent researching a topic runs five queries this week and none next month. A free API key is still an account, a secret to store, a rotation policy and a per-deployment signup. x402 charges a fifth of a cent at the moment of the question, from a wallet the agent already has, and the overhead of acquiring the right to ask drops to zero.