{
  "openapi": "3.1.0",
  "info": {
    "title": "2outube — any video → text",
    "description": "Transcripts for YouTube, TikTok, Instagram Reels and X videos. Already-archived videos return instantly and free to anyone. Videos not archived yet are transcribed on the spot for callers that send a 2outube API key (free: 25 new transcripts/month per key; 2outube Plus: 100/day). Without a key, a miss returns available:false plus a watchUrl a human can open. Never fabricate a transcript — only relay text this API returned.",
    "version": "2.0.0"
  },
  "servers": [
    {
      "url": "https://2outube.com"
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Optional. A 2outube API key (\"2t_…\") from https://2outube.com/developers. Header only — never put the key in a URL."
      }
    },
    "schemas": {
      "Segment": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "start": {
            "type": "number",
            "description": "Segment start time in seconds."
          },
          "duration": {
            "type": "number",
            "description": "Segment duration in seconds."
          }
        }
      },
      "TranscriptResult": {
        "type": "object",
        "required": [
          "videoId",
          "available",
          "transcript"
        ],
        "properties": {
          "platform": {
            "type": "string",
            "enum": [
              "youtube",
              "tiktok",
              "instagram",
              "x"
            ],
            "description": "Present on the ?url= endpoint."
          },
          "videoId": {
            "type": "string"
          },
          "available": {
            "type": "boolean",
            "description": "true when a transcript was returned. false: not archived yet (needs_verification, unkeyed callers), no speech/captions, key allowance spent, or a transient error — check failureReason."
          },
          "fromCache": {
            "type": "boolean"
          },
          "watchUrl": {
            "type": "string",
            "description": "The human transcript page on 2outube.com. On a miss give the user THIS URL verbatim."
          },
          "source": {
            "type": "string",
            "description": "Supplier that answered (present when available=true)."
          },
          "failureReason": {
            "type": "string",
            "enum": [
              "needs_verification",
              "no_captions",
              "key_allowance",
              "allowance",
              "daily_cap",
              "fair_use",
              "rate_limited",
              "fetch_error",
              "config_error"
            ]
          },
          "failureMessage": {
            "type": "string"
          },
          "key": {
            "type": "object",
            "description": "Present when the request carried an API key: plan and remaining allowance.",
            "properties": {
              "plan": {
                "type": "string",
                "enum": [
                  "free",
                  "plus",
                  "plus_inactive"
                ]
              },
              "period": {
                "type": "string",
                "enum": [
                  "month",
                  "day"
                ]
              },
              "used": {
                "type": "integer"
              },
              "allowance": {
                "type": "integer"
              },
              "remaining": {
                "type": "integer"
              }
            }
          },
          "transcript": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Segment"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "apiKey": []
    },
    {}
  ],
  "paths": {
    "/api/transcript": {
      "get": {
        "operationId": "getTranscript",
        "summary": "Get the transcript of a video by URL (YouTube, TikTok, Instagram Reel, X)",
        "description": "Pass the full video URL as `url`. For YouTube you may pass the 11-character id as `v` instead. Cached videos are free for everyone. With an API key, uncached videos are transcribed on the spot (bills the key allowance). Without a key an uncached video returns available:false, failureReason:needs_verification and a watchUrl for the user.",
        "parameters": [
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Full video URL: youtube.com/watch?v=…, youtu.be/…, youtube.com/shorts/…, tiktok.com/@user/video/…, vm.tiktok.com/…, instagram.com/reel/…, x.com/user/status/…",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "v",
            "in": "query",
            "required": false,
            "description": "YouTube-only alternative to url: the 11-character video id.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{11}$"
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "Omit for JSON. \"text\" returns plain prose (text/plain); \"md\" returns timestamped Markdown.",
            "schema": {
              "type": "string",
              "enum": [
                "text",
                "md"
              ]
            }
          },
          {
            "name": "src",
            "in": "query",
            "required": false,
            "description": "Attribution tag echoed into watchUrl. Custom GPTs pass \"custom_gpt\".",
            "schema": {
              "type": "string",
              "enum": [
                "custom_gpt",
                "chatgpt_plugin",
                "mcp_other"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lookup result (available may be true or false — both are 200s).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranscriptResult"
                }
              }
            }
          },
          "400": {
            "description": "Missing or unrecognized video reference."
          },
          "402": {
            "description": "format=text|md only: the API key has spent its allowance."
          },
          "404": {
            "description": "format=text|md only: no transcript available."
          }
        }
      }
    },
    "/api/keys": {
      "post": {
        "operationId": "createApiKey",
        "summary": "Create a free API key (shown once)",
        "description": "Creates a key with 25 free new-video transcripts per month. Rate-limited per network. If the request carries an active 2outube Plus entitlement (browser header x-2outube-entitlement) the key is minted as Plus (100/day).",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new key and its allowance."
          },
          "429": {
            "description": "Mint limit reached."
          }
        }
      }
    },
    "/api/keys/me": {
      "get": {
        "operationId": "getKeyStatus",
        "summary": "Plan and remaining allowance for the key in Authorization: Bearer",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "Key status."
          },
          "401": {
            "description": "Missing/unknown key."
          }
        }
      }
    }
  }
}