{
  "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": []
    }
  ],
  "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",
                      "date": "2024-04-10T00:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "No credits remaining. Search requires a positive credit balance but does not deduct credits.",
            "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",
            "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 — 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",
            "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 — 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"
      }
    },
    "schemas": {
      "SearchResult": {
        "type": "object",
        "required": ["id", "title", "podcast", "date"],
        "properties": {
          "id": {
            "type": "string",
            "description": "Episode ID for use with /transcripts/{id}"
          },
          "title": {
            "type": "string"
          },
          "podcast": {
            "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"
              }
            }
          }
        }
      }
    }
  }
}
