{
  "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 responses points here.",
        "security": [],
        "parameters": [
          {
            "name": "key",
            "in": "query",
            "required": true,
            "description": "Your API key (starts with pt_)",
            "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?key=pt_your_key",
                    "top_up_packs": {
                      "100": {
                        "url": "https://spoken.md/top-up?key=pt_your_key&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"
                }
              }
            }
          }
        }
      }
    },
    "/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 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?key=pt_your_key"
                  }
                }
              }
            }
          },
          "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"
      }
    },
    "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"
              }
            }
          }
        }
      }
    }
  }
}
