Every API here is available over the APIs.io API and to AI agents over MCP.
{
"openapi": "3.1.0",
"info": {
"title": "Podcast Transcripts API",
"description": "Podcast transcripts as clean markdown. Built for AI agents. Use the demo key `pt_demo` to try it free (demo episode only).",
"version": "1.0.0",
"contact": {
"url": "https://spoken.md"
}
},
"servers": [
{
"url": "https://spoken.md"
}
],
"security": [
{
"apiKey": []
},
{
"bearerApiKey": []
}
],
"paths": {
"/search": {
"get": {
"operationId": "searchEpisodes",
"summary": "Search podcast episodes",
"description": "Search by text query or paste a URL (Spotify, YouTube, etc.) to find matching podcast episodes. Returns episode IDs for use with /transcripts/{id}. Requires API key but does not charge credits.",
"parameters": [
{
"name": "q",
"in": "query",
"required": true,
"description": "Search query (text) or a podcast episode URL from any platform",
"schema": {
"type": "string",
"example": "huberman sleep"
}
}
],
"responses": {
"200": {
"description": "Matching episodes",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SearchResult"
}
}
}
},
"example": {
"results": [
{
"id": "1000651996090",
"title": "Dr. Matt Walker: Protocols to Improve Your Sleep",
"podcast": "Huberman Lab",
"podcastId": "1545953110",
"date": "2024-04-10T00:00:00Z"
}
]
}
}
}
},
"401": {
"description": "Missing or invalid API key",
"headers": {
"WWW-Authenticate": {
"description": "Authentication challenge (RFC 9110 15.5.2). Retry with an API key in the x-api-key header, or as an Authorization: Bearer token. The resource_metadata parameter (RFC 9728 5.1) points at the Protected Resource Metadata document.",
"schema": {
"type": "string",
"example": "Bearer realm=\"spoken.md\", charset=\"UTF-8\", resource_metadata=\"https://spoken.md/.well-known/oauth-protected-resource\", ApiKey realm=\"spoken.md\", charset=\"UTF-8\""
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/podcasts/{podcastId}/episodes": {
"get": {
"operationId": "listPodcastEpisodes",
"summary": "List a show's full back-catalog",
"description": "Returns a show's episodes, newest first, for use with /transcripts/{id}. Take the podcastId from any /search result. Requires API key but does not charge credits. To transcribe a whole show, loop the returned ids through /transcripts/{id}.",
"parameters": [
{
"name": "podcastId",
"in": "path",
"required": true,
"description": "Show ID (numeric), as returned in the podcastId field of /search results",
"schema": {
"type": "string",
"pattern": "^\\d+$",
"example": "1545953110"
}
}
],
"responses": {
"200": {
"description": "The show's complete episode list",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"podcast",
"podcast_id",
"count",
"episodes"
],
"properties": {
"podcast": {
"type": "string",
"description": "Show name"
},
"podcast_id": {
"type": "string"
},
"count": {
"type": "integer",
"description": "Number of episodes returned"
},
"episodes": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EpisodeRef"
}
}
}
},
"example": {
"podcast": "Huberman Lab",
"podcast_id": "1545953110",
"count": 2,
"episodes": [
{
"id": "1000651996090",
"title": "Dr. Matt Walker: Protocols to Improve Your Sleep",
"date": "2024-04-10T00:00:00Z"
}
]
}
}
}
},
"401": {
"description": "Missing or invalid API key",
"headers": {
"WWW-Authenticate": {
"description": "Authentication challenge (RFC 9110 15.5.2). Retry with an API key in the x-api-key header, or as an Authorization: Bearer token. The resource_metadata parameter (RFC 9728 5.1) points at the Protected Resource Metadata document.",
"schema": {
"type": "string",
"example": "Bearer realm=\"spoken.md\", charset=\"UTF-8\", resource_metadata=\"https://spoken.md/.well-known/oauth-protected-resource\", ApiKey realm=\"spoken.md\", charset=\"UTF-8\""
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"404": {
"description": "No podcast found for that id",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/buy": {
"post": {
"operationId": "buyCredits",
"summary": "Purchase API credits",
"description": "Creates a new API key and redirects to Stripe Checkout. After payment, the API key is displayed on the success page. New customer pricing: 100 for $15, 500 for $50, 2,000 for $160.",
"security": [],
"parameters": [
{
"name": "pack",
"in": "query",
"required": false,
"description": "Pack size (default: 100)",
"schema": {
"type": "string",
"enum": [
"100",
"500",
"2000"
],
"default": "100"
}
}
],
"responses": {
"302": {
"description": "Redirect to Stripe Checkout"
}
}
}
},
"/top-up": {
"post": {
"operationId": "topUpCredits",
"summary": "Top up credits for an existing API key",
"description": "Redirects to Stripe Checkout to purchase more credits for an existing key. Returning customer pricing: 100 for $10, 500 for $40, 2,000 for $120. The top_up_url in 402 and /balance responses points here; send the key the way you send it everywhere, in the x-api-key or Authorization header. The ?key= form is for links.",
"security": [
{
"apiKey": []
},
{
"bearerApiKey": []
},
{}
],
"parameters": [
{
"name": "key",
"in": "query",
"required": false,
"description": "Your API key (starts with pt_), for a link that cannot carry a header. Not needed when the key is sent in a header.",
"schema": {
"type": "string"
}
},
{
"name": "pack",
"in": "query",
"required": false,
"description": "Pack size (default: 100)",
"schema": {
"type": "string",
"enum": [
"100",
"500",
"2000"
],
"default": "100"
}
}
],
"responses": {
"302": {
"description": "Redirect to Stripe Checkout"
}
}
}
},
"/balance": {
"get": {
"operationId": "getBalance",
"summary": "Check credit balance and usage",
"description": "Returns current credit balance, account email, top-up links, and recent usage history. Use this to check remaining credits before or after fetching transcripts.",
"responses": {
"200": {
"description": "Balance and account info",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"credits",
"top_up"
],
"properties": {
"credits": {
"type": "integer",
"description": "Credits remaining"
},
"email": {
"type": "string"
},
"top_up": {
"type": "object",
"properties": {
"top_up_url": {
"type": "string"
},
"top_up_packs": {
"type": "object"
}
}
},
"usage": {
"type": "object",
"description": "Recent transcript access history",
"properties": {
"total": {
"type": "integer"
},
"recent": {
"type": "array",
"items": {
"type": "object",
"properties": {
"episodeId": {
"type": "string"
},
"accessedAt": {
"type": "string",
"format": "date-time"
}
}
}
}
}
}
}
},
"example": {
"credits": 96,
"email": "user@example.com",
"top_up": {
"top_up_url": "https://spoken.md/top-up",
"top_up_packs": {
"100": {
"url": "https://spoken.md/top-up?pack=100",
"price": "$10"
}
}
}
}
}
}
},
"401": {
"description": "Missing or invalid API key",
"headers": {
"WWW-Authenticate": {
"description": "Authentication challenge (RFC 9110 15.5.2). Retry with an API key in the x-api-key header, or as an Authorization: Bearer token. The resource_metadata parameter (RFC 9728 5.1) points at the Protected Resource Metadata document.",
"schema": {
"type": "string",
"example": "Bearer realm=\"spoken.md\", charset=\"UTF-8\", resource_metadata=\"https://spoken.md/.well-known/oauth-protected-resource\", ApiKey realm=\"spoken.md\", charset=\"UTF-8\""
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
},
"/following": {
"get": {
"operationId": "listFollowing",
"summary": "List the shows you follow",
"description": "The shows this account is kept current on. Every charged fetch makes its show a follow; the five most-fetched shows from the last 180 days are followed automatically (`source: fetch`), and up to 25 more can be declared with PUT /following/{podcastId} (`source: explicit`). Muted shows are listed separately. Requires a real key, charges nothing, never cached.",
"responses": {
"200": {
"description": "Followed and muted shows",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"following",
"muted",
"limits"
],
"properties": {
"following": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Follow"
}
},
"muted": {
"type": "array",
"items": {
"type": "object",
"properties": {
"podcast_id": {
"type": "string"
},
"podcast": {
"type": "string"
}
}
}
},
"limits": {
"type": "object",
"properties": {
"explicit": {
"type": "integer",
"description": "Maximum declared follows"
},
"inferred": {
"type": "integer",
"description": "Maximum automatic follows"
},
"inferred_window_days": {
"type": "integer",
"description": "How long a fetch keeps an automatic follow alive"
}
}
}
}
},
"example": {
"following": [
{
"podcast_id": "1469759170",
"podcast": "All-In with Chamath, Jason, Sacks & Friedberg",
"source": "fetch",
"fetch_count": 42,
"last_fetched_at": "2026-09-20T14:02:11.000Z",
"newest_fetched_id": "1000780992629"
}
],
"muted": [
{
"podcast_id": "1200361736",
"podcast": "The Daily"
}
],
"limits": {
"explicit": 25,
"inferred": 5,
"inferred_window_days": 180
}
}
}
}
},
"401": {
"description": "Missing or invalid API key (the `pt_demo` key has nothing to follow with)"
}
}
}
},
"/following/{podcastId}": {
"parameters": [
{
"name": "podcastId",
"in": "path",
"required": true,
"description": "Show ID (a `podcastId` from /search)",
"schema": {
"type": "string"
}
}
],
"put": {
"operationId": "followPodcast",
"summary": "Follow a show",
"description": "Declare a follow, or clear a mute. A declared follow stays active until you DELETE it, regardless of fetch activity, and does not use one of the five automatic slots. For a show you have never fetched, what counts as new starts from its newest episode at the time you follow it. Charges nothing.",
"responses": {
"200": {
"description": "Now following",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"podcast_id": {
"type": "string"
},
"podcast": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"following"
]
},
"source": {
"type": "string",
"enum": [
"explicit"
]
}
}
},
"example": {
"podcast_id": "1469759170",
"podcast": "All-In with Chamath, Jason, Sacks & Friedberg",
"state": "following",
"source": "explicit"
}
}
}
},
"401": {
"description": "Missing or invalid API key"
},
"404": {
"description": "No podcast with that ID"
},
"409": {
"description": "Declared-follow limit reached; unfollow one first. Body carries `limit`."
},
"502": {
"description": "Upstream error resolving the show; safe to retry"
}
}
},
"delete": {
"operationId": "unfollowPodcast",
"summary": "Stop following a show",
"description": "Mutes the show: it leaves your follows and is not re-added by later fetches. PUT /following/{podcastId} reverses it. Charges nothing.",
"responses": {
"200": {
"description": "Muted: the show no longer appears in your follows and is not re-added by later fetches",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"podcast_id": {
"type": "string"
},
"podcast": {
"type": "string"
},
"state": {
"type": "string",
"enum": [
"muted"
]
}
}
},
"example": {
"podcast_id": "1200361736",
"podcast": "The Daily",
"state": "muted"
}
}
}
},
"401": {
"description": "Missing or invalid API key"
},
"404": {
"description": "You are not following that show (error code `not_following`)"
}
}
}
},
"/new": {
"get": {
"operationId": "listNew",
"summary": "What is new on the shows you follow",
"description": "Every followed show (see /following) with the episodes released in the last 90 days that are newer than the newest episode you have fetched from it, up to the 10 newest per show, each with its transcript URL. Episodes with no transcript are left out. Reads a cache refreshed hourly; a show followed moments ago may list with no episodes until its first poll. Polling this route is free; fetching a transcript_url costs the normal credit. Send `Accept: application/atom+xml` or `?format=atom` for an Atom feed of the same episodes, which any feed reader or automation tool can subscribe to using HTTP Basic auth with the key as the username. Requires a real key, never cached.",
"parameters": [
{
"name": "format",
"in": "query",
"required": false,
"description": "`atom` for an Atom feed instead of JSON",
"schema": {
"type": "string",
"enum": [
"atom"
]
}
}
],
"responses": {
"200": {
"description": "New episodes, grouped by show",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"as_of",
"count",
"shows"
],
"properties": {
"as_of": {
"type": "string",
"format": "date-time"
},
"count": {
"type": "integer",
"description": "Episodes across all shows"
},
"shows": {
"type": "array",
"items": {
"type": "object",
"required": [
"podcast_id",
"podcast",
"source",
"episodes"
],
"properties": {
"podcast_id": {
"type": "string"
},
"podcast": {
"type": "string"
},
"source": {
"type": "string",
"enum": [
"fetch",
"explicit"
]
},
"episodes": {
"type": "array",
"items": {
"$ref": "#/components/schemas/NewEpisode"
}
}
}
}
}
}
},
"example": {
"as_of": "2026-09-24T14:00:00.000Z",
"count": 1,
"shows": [
{
"podcast_id": "1469759170",
"podcast": "All-In with Chamath, Jason, Sacks & Friedberg",
"source": "fetch",
"episodes": [
{
"id": "1000780992629",
"title": "E245: The AI capex debate",
"date": "2026-09-23T04:00:00Z",
"transcript_url": "https://spoken.md/transcripts/1000780992629"
}
]
}
]
}
},
"application/atom+xml": {
"schema": {
"type": "string",
"description": "An Atom 1.0 feed: one entry per episode, the entry link is the transcript URL"
}
}
}
},
"401": {
"description": "Missing or invalid API key"
}
}
}
},
"/transcripts/{episodeId}": {
"get": {
"operationId": "getTranscript",
"summary": "Get podcast episode transcript",
"description": "Returns a clean markdown transcript with speaker names and timestamps for any podcast episode. Use the demo key `pt_demo` to try the demo episode for free. All other episodes require a paid API key and cost 1 credit on first fetch \u2014 repeat fetches of the same episode are free.",
"parameters": [
{
"name": "episodeId",
"in": "path",
"required": true,
"description": "Podcast episode ID (numeric)",
"schema": {
"type": "string",
"pattern": "^\\d+$",
"example": "1000651996090"
}
}
],
"responses": {
"200": {
"description": "Transcript as markdown with speaker names and timestamps",
"headers": {
"X-Credits-Remaining": {
"description": "Number of credits remaining on this API key",
"schema": {
"type": "integer"
}
},
"X-Credits-Charged": {
"description": "Credits charged for this request (0 for demo episode or repeat fetch, 1 for first fetch)",
"schema": {
"type": "integer",
"enum": [
0,
1
]
}
},
"X-Top-Up-Url": {
"description": "Direct top-up URL, present when credits are low (10 or fewer)",
"schema": {
"type": "string",
"format": "uri"
}
}
},
"content": {
"text/markdown": {
"schema": {
"type": "string"
},
"example": "**John Smith** (0:00)\nWelcome to the show. Today we're talking about...\n\n**Jane Doe** (0:15)\nThanks for having me.\n"
}
}
},
"401": {
"description": "Missing or invalid API key",
"headers": {
"WWW-Authenticate": {
"description": "Authentication challenge (RFC 9110 15.5.2). Retry with an API key in the x-api-key header, or as an Authorization: Bearer token. The resource_metadata parameter (RFC 9728 5.1) points at the Protected Resource Metadata document.",
"schema": {
"type": "string",
"example": "Bearer realm=\"spoken.md\", charset=\"UTF-8\", resource_metadata=\"https://spoken.md/.well-known/oauth-protected-resource\", ApiKey realm=\"spoken.md\", charset=\"UTF-8\""
}
}
},
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"code": "unauthorized",
"message": "API key required. Pass it via the X-Api-Key header. Try the demo key to get started.",
"demo_key": "pt_demo",
"purchase_url": "https://spoken.md/"
}
}
}
}
},
"402": {
"description": "No credits remaining. POST to the top_up_url, with the key in the header as usual, to purchase more.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
},
"example": {
"error": {
"code": "payment_required",
"message": "No credits remaining. Your API key and cached transcripts are preserved \u2014 top up to resume.",
"top_up_url": "https://spoken.md/top-up"
}
}
}
}
},
"404": {
"description": "Episode not found or has no transcript",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
},
"502": {
"description": "Upstream error. No credit charged. Safe to retry.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ErrorResponse"
}
}
}
}
}
}
}
},
"components": {
"securitySchemes": {
"apiKey": {
"type": "apiKey",
"in": "header",
"name": "x-api-key",
"description": "API key (starts with pt_). Use `pt_demo` for free trial or purchase at https://spoken.md"
},
"bearerApiKey": {
"type": "http",
"scheme": "bearer",
"description": "The same pt_ API key sent as an RFC 6750 bearer token, for clients that only support Authorization headers. Equivalent to the x-api-key scheme \u2014 not an OAuth token. Registration: https://spoken.md/auth.md"
},
"basicApiKey": {
"type": "http",
"scheme": "basic",
"description": "The same pt_ API key as the HTTP Basic username with an empty password (or as the password with any username). Exists for feed readers subscribing to /new?format=atom, which cannot set custom headers. Equivalent to the x-api-key scheme."
}
},
"schemas": {
"SearchResult": {
"type": "object",
"required": [
"id",
"title",
"podcast",
"podcastId",
"date"
],
"properties": {
"id": {
"type": "string",
"description": "Episode ID for use with /transcripts/{id}"
},
"title": {
"type": "string"
},
"podcast": {
"type": "string"
},
"podcastId": {
"type": "string",
"description": "Show ID for use with /podcasts/{podcastId}/episodes"
},
"date": {
"type": "string",
"format": "date-time"
}
}
},
"EpisodeRef": {
"type": "object",
"required": [
"id",
"title",
"date"
],
"properties": {
"id": {
"type": "string",
"description": "Episode ID for use with /transcripts/{id}"
},
"title": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time"
}
}
},
"ErrorResponse": {
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "string",
"enum": [
"unauthorized",
"payment_required",
"not_found",
"upstream_error",
"bad_request"
]
},
"message": {
"type": "string"
},
"demo_key": {
"type": "string"
},
"purchase_url": {
"type": "string"
},
"top_up_url": {
"type": "string"
}
}
}
}
},
"Follow": {
"type": "object",
"required": [
"podcast_id",
"podcast",
"source",
"fetch_count"
],
"properties": {
"podcast_id": {
"type": "string",
"description": "Show ID, for /podcasts/{podcastId}/episodes and /following/{podcastId}"
},
"podcast": {
"type": "string"
},
"source": {
"type": "string",
"enum": [
"fetch",
"explicit"
],
"description": "`fetch`: inferred from yo
# --- truncated at 32 KB (33 KB total) ---
# Full source: https://artifacts.apis.io/spoken-md/openapi/_original/spoken-md-well-known-openapi.json