{
  "openapi": "3.1.0",
  "info": {
    "title": "InstaScript public read API",
    "version": "1.0.0",
    "summary": "Read-only endpoints that expose InstaScript's public pages and its measured in-browser transcription times.",
    "description": "InstaScript turns video and audio files into text inside the browser. These endpoints expose only public, read-only data: the site's page catalog and the measured transcription benchmark. No key is required. There are no write operations, no accounts and no payment flows.",
    "contact": { "email": "contact@instascript.app" },
    "license": { "name": "MIT", "url": "https://github.com/nickwebt800/instascript/blob/main/LICENSE" }
  },
  "servers": [ { "url": "https://instascript.app", "description": "Production" } ],
  "security": [],
  "tags": [
    { "name": "pages", "description": "Public page catalog" },
    { "name": "benchmark", "description": "Historical single-model timings with provenance and a link to current two-model measurements" },
    { "name": "media", "description": "Public Instagram media resolution" }
  ],
  "paths": {
    "/api/pages": {
      "get": {
        "tags": ["pages"],
        "summary": "Find the public page that matches a task",
        "operationId": "findPage",
        "parameters": [
          { "name": "q", "in": "query", "required": false,
            "schema": { "type": "string" },
            "description": "Free-text words describing the job, for example 'tiktok' or 'mp3 to text'. Omit to list every page." }
        ],
        "responses": {
          "200": {
            "description": "Matching pages, most relevant first. An empty query or no match still returns 200.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PageResult" } } }
          }
        }
      }
    },
    "/api/benchmark": {
      "get": {
        "tags": ["benchmark"],
        "summary": "Historical 2026-09-14 single-model transcription times",
        "operationId": "getBenchmark",
        "parameters": [
          { "name": "length_seconds", "in": "query", "required": false,
            "schema": { "type": "number", "exclusiveMinimum": 0 },
            "description": "Return the closest observed historical whisper-tiny.en-only row, not interpolation or a current end-to-end prediction. Omit to return all historical rows." }
        ],
        "responses": {
          "200": {
            "description": "Historical single-model rows retain the existing schema. Provenance identifies the date and pipeline; current_source_url links separate current two-model observations. Each historical row is a single run, not an average.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BenchmarkResult" } } }
          },
          "400": { "description": "length_seconds is not a positive number." }
        }
      }
    },
    "/api/extract": {
      "post": {
        "tags": ["media"],
        "summary": "Resolve a public Instagram URL to a media URL and caption",
        "operationId": "extractInstagram",
        "requestBody": {
          "required": true,
          "content": { "application/json": {
            "schema": { "type": "object", "required": ["url"],
              "properties": { "url": { "type": "string", "format": "uri",
                "description": "A public Instagram post or Reel URL. Other hosts are rejected with 400." } } }
          } }
        },
        "responses": {
          "200": { "description": "Media URL, caption and type.",
            "content": { "application/json": { "schema": { "type": "object",
              "properties": { "videoUrl": { "type": "string" }, "caption": { "type": "string" },
                              "type": { "type": "string" } } } } } },
          "400": { "description": "Missing, malformed or non-Instagram URL." },
          "404": { "description": "The public post was reachable, but no usable media or caption was found." },
          "500": { "description": "The extraction service encountered an unexpected error." },
          "502": { "description": "Instagram or a fallback fetch service could not be reached." }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "PageResult": {
        "type": "object",
        "properties": {
          "query": { "type": "string" },
          "count": { "type": "integer" },
          "source_url": { "type": "string", "format": "uri" },
          "pages": { "type": "array", "items": { "$ref": "#/components/schemas/Page" } }
        }
      },
      "Page": {
        "type": "object",
        "properties": {
          "title": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "summary": { "type": "string" },
          "accepts": { "type": "array", "items": { "type": "string" } },
          "limits": { "type": "array", "items": { "type": "string" } }
        }
      },
      "BenchmarkResult": {
        "type": "object",
        "properties": {
          "note": { "type": "string" },
          "source_url": { "type": "string", "format": "uri" },
          "measurement_date": { "type": "string", "format": "date" },
          "pipeline": { "type": "string", "description": "Historical pipeline measured by rows." },
          "current_pipeline": { "type": "string", "description": "Current workflow; not measured by the historical rows." },
          "current_source_url": { "type": "string", "format": "uri", "description": "Separate current two-model measurements." },
          "method_url": { "type": "string", "format": "uri" },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "run": { "type": "integer" },
                "audio_seconds": { "type": "number" },
                "wall_clock_seconds": { "type": "number" },
                "includes_model_load": { "type": "boolean" }
              }
            }
          }
        }
      }
    }
  }
}
