{
  "openapi": "3.1.0",
  "info": {
    "title": "PodcastsToText API",
    "version": "1.1.0",
    "description": "Transcribe podcasts and audio files to text.\n\nTranscription is asynchronous: POST a job, get an id back immediately, then poll until status is \"completed\". Responses never include a full transcript unless you ask for it with ?include=text, bodies routinely run to tens of thousands of characters.\n\nPOST /api/v1/transcriptions always answers 202, but `status` may already be \"completed\": a YouTube video is transcribed from its existing caption track, which finishes inline and costs no credits. Branch on `status`, never on the status code: whether a video has captions is a property of the video, so the same request can go either way.\n\nEvery response carries RateLimit-* headers. Limits are per key, per minute and per day, and vary by plan.\n\nThe API and the MCP server are on Creator, Pro, Studio and Podmaxxing. What differs between them is the monthly minute allowance and the rate limits. Extra API minutes can be purchased; they are spendable through this API and MCP only, not from the web app, and only while the plan includes API access.\n\nThere is also an MCP server at /api/mcp using the same API keys.",
    "contact": {
      "email": "support@podcaststotext.com",
      "url": "https://podcaststotext.com"
    }
  },
  "servers": [
    {
      "url": "https://podcaststotext.com"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A PodcastsToText API key (ptt_…). Create one at /dashboard/api-keys."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable identifier: branch on this, not the message"
              },
              "message": {
                "type": "string"
              },
              "how_to_fix": {
                "type": "string",
                "description": "What to change before retrying"
              }
            }
          }
        }
      },
      "Transcription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "duration_seconds": {
            "type": "integer",
            "nullable": true
          },
          "format": {
            "type": "string"
          },
          "source_type": {
            "type": "string",
            "nullable": true
          },
          "source_url": {
            "type": "string",
            "nullable": true
          },
          "image_url": {
            "type": "string",
            "nullable": true
          },
          "show_id": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "transcript_url": {
            "type": "string",
            "nullable": true,
            "description": "Where the stored transcript lives"
          },
          "error": {
            "type": "string",
            "description": "Why the job failed. Only present when status is \"failed\"."
          },
          "preview": {
            "type": "string",
            "description": "First ~500 characters. Present when the transcript is readable."
          },
          "text": {
            "type": "string",
            "description": "Full body. Only present with ?include=text."
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "Stable machine-readable identifier: branch on this, not the message"
                    },
                    "message": {
                      "type": "string"
                    },
                    "how_to_fix": {
                      "type": "string",
                      "description": "What to change before retrying"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "Account is out of credits",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "Stable machine-readable identifier: branch on this, not the message"
                    },
                    "message": {
                      "type": "string"
                    },
                    "how_to_fix": {
                      "type": "string",
                      "description": "What to change before retrying"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource on this account",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "Stable machine-readable identifier: branch on this, not the message"
                    },
                    "message": {
                      "type": "string"
                    },
                    "how_to_fix": {
                      "type": "string",
                      "description": "What to change before retrying"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "PlanRequired": {
        "description": "The account's plan does not allow this: either it includes no API access, or the requested scope needs write access. `error.code` is \"plan_required\". No plan is in that state today, so in practice you will see 402 insufficient_credits instead, which means the plan is fine and the balance is empty.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "Stable machine-readable identifier: branch on this, not the message"
                    },
                    "message": {
                      "type": "string"
                    },
                    "how_to_fix": {
                      "type": "string",
                      "description": "What to change before retrying"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Credit-spending operations are paused system-wide for the rest of the day while unusual load is investigated. `error.code` is \"service_unavailable\". This is NOT a limit on the calling account, and upgrading does not lift it. Honour Retry-After. It is deliberately not a 429 so a client cannot mistake it for its own budget.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until spending resumes"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "Stable machine-readable identifier: branch on this, not the message"
                    },
                    "message": {
                      "type": "string"
                    },
                    "how_to_fix": {
                      "type": "string",
                      "description": "What to change before retrying"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests. Limits are per API key, counted per minute and per day, and depend on the plan. Operations that cost money to run (transcription, renders, AI) carry a second, tighter limit of their own. Read RateLimit-Remaining on every successful response to pace requests rather than waiting to be refused.",
        "headers": {
          "RateLimit-Limit": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests permitted in the current window"
          },
          "RateLimit-Remaining": {
            "schema": {
              "type": "integer"
            },
            "description": "Requests left in it"
          },
          "RateLimit-Reset": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds until the window rolls over"
          },
          "RateLimit-Policy": {
            "schema": {
              "type": "string"
            },
            "description": "e.g. \"120;w=60, 10000;w=86400\""
          },
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds to wait. Present on 429 only."
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "error": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "description": "Stable machine-readable identifier: branch on this, not the message"
                    },
                    "message": {
                      "type": "string"
                    },
                    "how_to_fix": {
                      "type": "string",
                      "description": "What to change before retrying"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/transcriptions": {
      "get": {
        "summary": "List transcriptions",
        "description": "Newest first. Metadata only, no transcript bodies.",
        "operationId": "listTranscriptions",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 50,
              "default": 20
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "processing",
                "completed",
                "failed"
              ]
            }
          },
          {
            "name": "show_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Opaque. Pass back the next_cursor from the previous page verbatim.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of transcriptions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "title": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "processing",
                              "completed",
                              "failed"
                            ]
                          },
                          "duration_seconds": {
                            "type": "integer",
                            "nullable": true
                          },
                          "format": {
                            "type": "string"
                          },
                          "source_type": {
                            "type": "string",
                            "nullable": true
                          },
                          "source_url": {
                            "type": "string",
                            "nullable": true
                          },
                          "image_url": {
                            "type": "string",
                            "nullable": true
                          },
                          "show_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "transcript_url": {
                            "type": "string",
                            "nullable": true,
                            "description": "Where the stored transcript lives"
                          },
                          "error": {
                            "type": "string",
                            "description": "Why the job failed. Only present when status is \"failed\"."
                          },
                          "preview": {
                            "type": "string",
                            "description": "First ~500 characters. Present when the transcript is readable."
                          },
                          "text": {
                            "type": "string",
                            "description": "Full body. Only present with ?include=text."
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Opaque cursor for the next page, or null on the last page. Do not parse it."
                    },
                    "next_actions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "summary": "Start a transcription",
        "description": "Returns immediately with an id: it does not wait for the transcript. Accepts a direct audio URL or a YouTube, TikTok, Spotify or Apple Podcasts link, which is resolved for you; use /api/v1/podcasts/lookup first only when you need to choose an episode from a show. An accepted request returns 202, a rejected one still answers 4xx, so check `status` rather than the HTTP code: a YouTube video with captions comes back already \"completed\" and costs nothing. Everything else spends credits.",
        "operationId": "createTranscription",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "anyOf": [
                  {
                    "required": [
                      "url"
                    ]
                  },
                  {
                    "required": [
                      "audio_url"
                    ]
                  }
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "What to transcribe: a Spotify or Apple Podcasts episode link, a YouTube or TikTok URL, or a direct audio file. Resolved for you."
                  },
                  "audio_url": {
                    "type": "string",
                    "description": "A direct audio file URL you have already resolved yourself. Taken at face value, no lookup, one fewer round trip. Use this or \"url\"."
                  },
                  "title": {
                    "type": "string",
                    "description": "Overrides the title resolved from the platform."
                  },
                  "language": {
                    "type": "string",
                    "description": "Language hint, e.g. \"en\". Auto-detected if omitted."
                  },
                  "speaker_labels": {
                    "type": "boolean",
                    "default": true,
                    "description": "Label distinct speakers. Paid plans only; ignored on free."
                  },
                  "duration_seconds": {
                    "type": "number",
                    "description": "Only used with a direct audio_url, where the file would otherwise be probed for its length."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "completed"
                      ],
                      "description": "Already \"completed\" for caption-sourced videos: do not poll those"
                    },
                    "source_type": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "next_actions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/PlanRequired"
          },
          "422": {
            "description": "The source has no transcribable audio (error.code \"source_unavailable\")",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "description": "The media could not be fetched; often transient (error.code \"source_unavailable\")",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/transcriptions/{id}": {
      "get": {
        "summary": "Get one transcription",
        "description": "Poll this after creating a job. Returns a preview by default; ?include=text for the whole body, ?format= for a subtitle or text file.",
        "operationId": "getTranscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include",
            "in": "query",
            "description": "Set to \"text\" for the full transcript",
            "schema": {
              "type": "string",
              "enum": [
                "text"
              ]
            }
          },
          {
            "name": "format",
            "in": "query",
            "description": "Return the transcript as a file instead of JSON. srt/vtt/json need a paid-plan transcript with timings; txt/docx/pdf work for any transcript.",
            "schema": {
              "type": "string",
              "enum": [
                "srt",
                "vtt",
                "txt",
                "json",
                "docx",
                "pdf"
              ]
            }
          },
          {
            "name": "speakers",
            "in": "query",
            "description": "Label each cue with its speaker in srt/vtt/txt, and head each paragraph with the name in docx/pdf. json always includes a speaker field when the transcript has diarization.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "timestamps",
            "in": "query",
            "description": "docx and pdf only: head each paragraph with its start time. srt and vtt are timings already, and json carries start and end on every cue.",
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The transcription",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "title": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "completed",
                        "failed"
                      ]
                    },
                    "duration_seconds": {
                      "type": "integer",
                      "nullable": true
                    },
                    "format": {
                      "type": "string"
                    },
                    "source_type": {
                      "type": "string",
                      "nullable": true
                    },
                    "source_url": {
                      "type": "string",
                      "nullable": true
                    },
                    "image_url": {
                      "type": "string",
                      "nullable": true
                    },
                    "show_id": {
                      "type": "string",
                      "nullable": true
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "transcript_url": {
                      "type": "string",
                      "nullable": true,
                      "description": "Where the stored transcript lives"
                    },
                    "error": {
                      "type": "string",
                      "description": "Why the job failed. Only present when status is \"failed\"."
                    },
                    "preview": {
                      "type": "string",
                      "description": "First ~500 characters. Present when the transcript is readable."
                    },
                    "text": {
                      "type": "string",
                      "description": "Full body. Only present with ?include=text."
                    }
                  }
                }
              },
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Unknown format, or a time-coded format asked of a transcript with no timings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No such transcription on this account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a transcription",
        "description": "Removes the row and its stored transcript file. Permanent, and it does not refund credits. A malformed id answers 404, the same as an id belonging to another account.",
        "operationId": "deleteTranscription",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    },
                    "storage_removed": {
                      "type": "boolean",
                      "description": "False when the row is gone but its stored object outlived it, surfaced so storage costs can be reconciled"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PlanRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/uploads": {
      "post": {
        "summary": "Get a presigned upload URL",
        "description": "For transcribing a local file. The file goes straight to storage and never through this API, so large uploads are not limited by request-body size. Two steps: presign here, PUT the bytes, then create a transcription with the returned audio_url.",
        "operationId": "createUpload",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "content_type",
                  "size_bytes"
                ],
                "properties": {
                  "filename": {
                    "type": "string",
                    "example": "episode.mp3"
                  },
                  "content_type": {
                    "type": "string",
                    "example": "audio/mpeg"
                  },
                  "size_bytes": {
                    "type": "integer",
                    "description": "Checked against your plan before presigning"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Presigned upload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "upload_url": {
                      "type": "string",
                      "description": "PUT the raw bytes here"
                    },
                    "audio_url": {
                      "type": "string",
                      "description": "Pass this to POST /api/v1/transcriptions"
                    },
                    "key": {
                      "type": "string"
                    },
                    "expires_in_seconds": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unsupported type, or too large for the plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/podcasts/feeds": {
      "get": {
        "summary": "Find a podcast RSS feed by name",
        "description": "The entry point when you have a show's name and nothing else. Returns ranked candidates rather than one match: show names collide, so check `author` before choosing.",
        "operationId": "findPodcastFeeds",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Podcast name",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "description": "Apple Podcasts link, or an RSS URL to pass through",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 20,
              "default": 5
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching podcasts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "author": {
                            "type": "string",
                            "nullable": true
                          },
                          "rss_url": {
                            "type": "string"
                          },
                          "artwork_url": {
                            "type": "string",
                            "nullable": true
                          },
                          "genre": {
                            "type": "string",
                            "nullable": true
                          },
                          "episode_count": {
                            "type": "integer",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "next_actions": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unsupported link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No podcast matched",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/podcasts/lookup": {
      "get": {
        "summary": "Resolve any supported link",
        "description": "Turn an RSS feed, Apple Podcasts, Spotify, YouTube or TikTok link into metadata and, where one exists, a direct audio URL. A YouTube video has no audio URL, its transcript comes from the caption track, so `audio_url` is null and `mode` is \"captions\"; pass the original link to POST /api/v1/transcriptions in that case. Despite the path, this is not podcast-only.",
        "operationId": "lookupPodcast",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "episode",
            "in": "query",
            "description": "Title fragment to pick one episode",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Show and episodes"
          },
          "400": {
            "description": "Unsupported or unresolvable link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/shows": {
      "get": {
        "summary": "List connected shows",
        "operationId": "listShows",
        "responses": {
          "200": {
            "description": "Connected podcast feeds"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/transcriptions/{id}/show-notes": {
      "post": {
        "summary": "Generate AI show notes",
        "description": "A summary plus bullet points for a completed transcript. Cached on the transcript: the second call returns `cached: true`, spends nothing, and is the cheapest way to read them back. Pass {\"force\": true} to regenerate. Requires the \"ai:write\" scope and a paid plan. POST rather than GET because the first call spends money, and a GET that bills is one a proxy or a prefetch can bill you for.",
        "operationId": "generateShowNotes",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "force": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Show notes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "show_notes": {
                      "type": "string"
                    },
                    "cached": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Transcript has no content yet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PlanRequired"
          },
          "404": {
            "description": "No such transcription on this account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/transcriptions/{id}/chapters": {
      "post": {
        "summary": "Generate chapter markers",
        "description": "Timestamped chapters: a title and a start time in seconds for each section. Cached like show notes. Needs a transcript with word timings, which is a property of the TRANSCRIPT rather than of the account: one made on the free tier has no segments to place chapters against, and upgrading does not change that. Requires the \"ai:write\" scope and a paid plan.",
        "operationId": "generateChapters",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "force": {
                    "type": "boolean",
                    "default": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Chapters",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "chapters": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "title": {
                            "type": "string"
                          },
                          "start": {
                            "type": "number",
                            "description": "Seconds from the start"
                          }
                        }
                      }
                    },
                    "cached": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Transcript has no word timings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PlanRequired"
          },
          "404": {
            "description": "No such transcription on this account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/transcriptions/{id}/translate": {
      "get": {
        "summary": "List generated translations",
        "description": "Which languages this transcript already holds, and how many more the plan allows. Free, and the only way to learn the cap without hitting it. Every language listed is free to fetch again. Requires the \"transcriptions:read\" scope.",
        "operationId": "listTranslations",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Languages held and remaining",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "languages": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "languages_used": {
                      "type": "integer"
                    },
                    "languages_limit": {
                      "type": "integer"
                    },
                    "languages_remaining": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "No such transcription on this account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Translate a transcript",
        "description": "Translate a completed transcript into one language. Cached per language: asking for a language already generated returns `cached: true`, spends nothing, and never counts against the limit. Each transcript may hold a capped number of DISTINCT languages, reported as languages_limit in the response, and exceeding it returns 403 `translation_limit_reached` naming the languages already stored. Pass {\"force\": true} to re-translate one in place. Requires the \"ai:write\" scope and a paid plan.",
        "operationId": "translateTranscript",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "language"
                ],
                "properties": {
                  "language": {
                    "type": "string",
                    "example": "Spanish"
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Re-translate a language already stored, replacing it. Bills again."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The translation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "language": {
                      "type": "string"
                    },
                    "translation": {
                      "type": "string"
                    },
                    "cached": {
                      "type": "boolean"
                    },
                    "languages_used": {
                      "type": "integer"
                    },
                    "languages_limit": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "No language given, or the transcript has no content",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Paid plan required, or the per-transcript language limit is reached",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such transcription on this account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/episodes/{id}/publish": {
      "post": {
        "summary": "Publish an episode",
        "description": "Put an episode's transcript on a public, indexable page. Idempotent: republishing returns the same URL and changes nothing, which is what makes it safe to retry. Only for shows whose ownership has been verified, because it publishes under the user's name. Requires the \"shows:write\" scope. Takes an EPISODE id from a connected show, not a transcription id.",
        "operationId": "publishEpisode",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string"
                    },
                    "already_public": {
                      "type": "boolean"
                    },
                    "title": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unverified show, or no transcript yet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PlanRequired"
          }
        }
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "summary": "List webhook endpoints",
        "description": "Also returns the closed list of available events, so you do not need the docs to subscribe.",
        "operationId": "listWebhooks",
        "responses": {
          "200": {
            "description": "Endpoints and the event catalogue",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "summary": "Register a webhook endpoint",
        "description": "Subscribe to events instead of polling for them, which is also how you stay inside the rate limits. The `secret` in the response is shown ONCE and is never retrievable; deliveries are signed with it (HMAC-SHA256 over the raw body). Requires the \"shows:write\" scope.",
        "operationId": "createWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "Public HTTPS endpoint. Private and link-local addresses are refused."
                  },
                  "description": {
                    "type": "string"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "transcription.completed",
                        "episode.detected",
                        "episode.published"
                      ]
                    },
                    "description": "Omit or leave empty to subscribe to all of them"
                  },
                  "payload": {
                    "type": "object",
                    "description": "Opt in to carrying the transcript in the delivery, for a receiver that cannot call the API back. Omitted above 256 KB, with text_omitted saying why.",
                    "properties": {
                      "text": {
                        "type": "boolean"
                      },
                      "format": {
                        "type": "string",
                        "enum": [
                          "txt",
                          "srt",
                          "vtt",
                          "json"
                        ],
                        "default": "txt"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint registered. Store `secret` now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "secret": {
                      "type": "string",
                      "description": "Shown once. Never returned again by any endpoint."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unusable URL or unknown event",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "description": "Stable machine-readable identifier: branch on this, not the message"
                        },
                        "message": {
                          "type": "string"
                        },
                        "how_to_fix": {
                          "type": "string",
                          "description": "What to change before retrying"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/PlanRequired"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}": {
      "patch": {
        "summary": "Update a webhook endpoint",
        "description": "Change its events, or disable and re-enable it. Re-enabling clears the recorded delivery error.",
        "operationId": "updateWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "disabled": {
                    "type": "boolean"
                  },
                  "payload": {
                    "type": "object",
                    "description": "Opt in to carrying the transcript in the delivery, for a receiver that cannot call the API back. Omitted above 256 KB, with text_omitted saying why.",
                    "properties": {
                      "text": {
                        "type": "boolean"
                      },
                      "format": {
                        "type": "string",
                        "enum": [
                          "txt",
                          "srt",
                          "vtt",
                          "json"
                        ],
                        "default": "txt"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Delete a webhook endpoint",
        "operationId": "deleteWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/deliveries": {
      "get": {
        "summary": "List deliveries for a webhook endpoint",
        "description": "Every attempt made to this endpoint, newest first. The answer to \"you say you sent it and my server never got it\". Response bodies and payloads are omitted here: fetch one delivery for those.",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by delivery state.",
            "schema": {
              "type": "string",
              "enum": [
                "queued",
                "delivering",
                "delivered",
                "failed"
              ]
            }
          },
          {
            "name": "event",
            "in": "query",
            "description": "Filter by event name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "A created_at value from a previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery log",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Unknown status or event filter"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/deliveries/{deliveryId}": {
      "get": {
        "summary": "Get one delivery",
        "description": "The full record, including the payload that was sent and the response body that came back (truncated to 2000 characters). `replay_blocked_reason` is null when a replay would be accepted.",
        "operationId": "getWebhookDelivery",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The delivery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/webhooks/{id}/deliveries/{deliveryId}/retry": {
      "post": {
        "summary": "Send a delivery again",
        "description": "Requeues a delivered or failed delivery, resetting its attempt count so it gets the full retry ladder again. The delivery id is unchanged, so a receiver deduplicating on it sees the same event. Refused for a delivery that is already queued or in flight, and for a disabled endpoint.",
        "operationId": "retryWebhookDelivery",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Already being sent, not requeued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "202": {
            "description": "Queued for delivery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Endpoint disabled, or the delivery is queued or in flight"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/usage": {
      "get": {
        "summary": "Credit balance and plan",
        "description": "Worth checking before a long episode or a batch.",
        "operationId": "getUsage",
        "responses": {
          "200": {
            "description": "Current usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": {
                      "type": "string"
                    },
                    "credits_remaining_minutes": {
                      "type": "number",
                      "description": "Total minutes this key can spend: the plan allowance plus any purchased minutes."
                    },
                    "plan_allowance_minutes": {
                      "type": "number",
                      "description": "The plan's monthly allowance. Resets each billing period and is also spendable in the web app."
                    },
                    "purchased_minutes": {
                      "type": "number",
                      "description": "Minutes bought as packs. They do not reset monthly, and are spendable through this API and MCP only, while the plan includes API access."
                    },
                    "transcriptions_total": {
                      "type": "integer"
                    },
                    "scopes": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    }
  }
}