{
  "openapi": "3.1.0",
  "info": {
    "title": "Clip Farm Twitch VOD connector API",
    "version": "1.0.0",
    "description": "A narrow, existing REST workflow for turning a person's Twitch VOD into clips. This is an API description for a proposed Muse integration, not a Meta connector manifest or approval. Quote first; submit only after the person approves the exact processing-credit cost."
  },
  "servers": [
    { "url": "https://tzjihxghiwzvliolpghm.supabase.co" }
  ],
  "security": [
    { "clipFarmApiKey": [] }
  ],
  "paths": {
    "/functions/v1/me": {
      "get": {
        "operationId": "getClipFarmAccount",
        "summary": "Confirm the connected Clip Farm account",
        "responses": {
          "200": { "description": "Account identity", "content": { "application/json": { "schema": { "type": "object", "properties": { "user": { "type": "object", "properties": { "id": { "type": "string" }, "email": { "type": "string" } } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/functions/v1/balance": {
      "get": {
        "operationId": "getClipFarmBalance",
        "summary": "Read remaining processing-credit seconds",
        "responses": {
          "200": { "description": "Credit balance", "content": { "application/json": { "schema": { "type": "object", "required": ["balance_sec"], "properties": { "balance_sec": { "type": "integer", "minimum": 0 } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/functions/v1/plan": {
      "get": {
        "operationId": "getClipFarmPlan",
        "summary": "Read plan and VOD limits",
        "responses": {
          "200": { "description": "Plan information", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/functions/v1/twitch/vods": {
      "get": {
        "operationId": "listRecentTwitchVods",
        "summary": "List recent Twitch VODs for a linked account",
        "description": "If Twitch is not linked, this returns 409. Ask the person for a VOD URL instead of trying to link an account through this API.",
        "responses": {
          "200": { "description": "Recent VODs", "content": { "application/json": { "schema": { "type": "object", "required": ["vods"], "properties": { "vods": { "type": "array", "items": { "type": "object", "additionalProperties": true } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "409": { "description": "Twitch is not linked" }
        }
      }
    },
    "/functions/v1/twitch/inspect": {
      "post": {
        "operationId": "inspectTwitchVod",
        "summary": "Inspect a Twitch VOD without spending credits",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/VodRequest" } } } },
        "responses": {
          "200": { "description": "Source inspection", "content": { "application/json": { "schema": { "type": "object", "required": ["inspection"], "properties": { "inspection": { "type": "object", "additionalProperties": true } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/functions/v1/jobs/quote": {
      "post": {
        "operationId": "quoteTwitchClipJob",
        "summary": "Validate settings and quote processing cost without creating a job",
        "description": "Quote the exact settings that will be submitted. cost_sec is processing-credit seconds, not currency. This call does not reserve credits and must not be treated as approval to submit.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClipSettings" } } } },
        "responses": {
          "200": { "description": "Read-only quote", "content": { "application/json": { "schema": { "type": "object", "required": ["cost_sec", "source", "settings", "quoted_at"], "properties": { "cost_sec": { "type": "integer", "minimum": 0 }, "source": { "type": "object", "additionalProperties": true }, "settings": { "type": "object", "additionalProperties": true }, "quoted_at": { "type": "string", "format": "date-time" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "400": { "description": "Invalid or unsupported clipping request" }
        }
      }
    },
    "/functions/v1/jobs": {
      "post": {
        "operationId": "submitTwitchClipJob",
        "summary": "Submit one approved, billable clip job",
        "description": "This spends Clip Farm processing credits. Call only after the person approves the exact VOD, settings and cost_sec from quoteTwitchClipJob. Retain the same Idempotency-Key when retrying an uncertain submission. On quote_changed, obtain a new quote and fresh user approval.",
        "parameters": [
          { "name": "Idempotency-Key", "in": "header", "required": true, "description": "Stable unique key for this approved submission; reuse only for retries of the same request.", "schema": { "type": "string", "minLength": 1, "maxLength": 128, "pattern": "^[A-Za-z0-9._:-]+$" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ApprovedClipJob" } } } },
        "responses": {
          "201": { "description": "Job accepted; processing is asynchronous", "content": { "application/json": { "schema": { "type": "object", "required": ["job"], "properties": { "job": { "$ref": "#/components/schemas/Job" }, "queue_position": { "type": "integer" }, "inspection": { "type": "object", "additionalProperties": true } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "description": "Insufficient credits" },
          "409": { "description": "Quote changed or idempotency key reused for different settings" },
          "429": { "description": "Creation rate limit reached" }
        }
      }
    },
    "/functions/v1/jobs/{job_id}": {
      "get": {
        "operationId": "getClipFarmJob",
        "summary": "Read one job and its current clips",
        "parameters": [
          { "name": "job_id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
        ],
        "responses": {
          "200": { "description": "Job and results", "content": { "application/json": { "schema": { "type": "object", "required": ["job", "results"], "properties": { "job": { "$ref": "#/components/schemas/Job" }, "results": { "type": "array", "items": { "$ref": "#/components/schemas/ClipResult" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Job not found for this account" }
        }
      }
    },
    "/functions/v1/media/sign": {
      "post": {
        "operationId": "signClipDownloads",
        "summary": "Get 15-minute download links for owned clip storage keys",
        "description": "Only send storage_key values returned by getClipFarmJob. Unknown or unowned keys are omitted from urls. Do not sign until the person wants to open or download clips.",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["keys"], "properties": { "keys": { "type": "array", "minItems": 1, "maxItems": 100, "items": { "type": "string" } } } } } } },
        "responses": {
          "200": { "description": "Short-lived signed URLs", "content": { "application/json": { "schema": { "type": "object", "required": ["urls"], "properties": { "urls": { "type": "object", "additionalProperties": { "type": "string", "format": "uri" } } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "clipFarmApiKey": { "type": "http", "scheme": "bearer", "description": "A Clip Farm API key created by the person in Settings → API keys. Enter only through Muse's secure credential flow." }
    },
    "responses": {
      "Unauthorized": { "description": "Invalid, missing, or revoked Clip Farm API key" }
    },
    "schemas": {
      "VodRequest": {
        "type": "object", "required": ["vod_url"], "additionalProperties": false,
        "properties": { "vod_url": { "type": "string", "format": "uri", "description": "A Twitch VOD URL supplied or selected by the person." } }
      },
      "ClipSettings": {
        "type": "object", "required": ["vod_url"], "additionalProperties": true,
        "properties": {
          "vod_url": { "type": "string", "format": "uri", "description": "A Twitch VOD URL." },
          "game": { "type": "string", "description": "Optional game or stream mode. Twitch games are normally detected automatically; use just_chatting or irl when appropriate." },
          "manual_only": { "type": "boolean", "description": "Use only the broadcaster's marked Twitch clips; no automatic moment detection." },
          "keep_landscape": { "type": "boolean", "description": "Render 16:9 instead of the standard vertical output." },
          "burn_chat": { "type": "boolean", "description": "Whether to burn Twitch chat replay into portrait clips." },
          "captions_enabled": { "type": "boolean", "description": "Burn captions into clips when true." },
          "caption_style": { "type": "string", "enum": ["classic", "highlight", "pop"] }
        }
      },
      "ApprovedClipJob": {
        "allOf": [
          { "$ref": "#/components/schemas/ClipSettings" },
          { "type": "object", "required": ["approved_cost_sec"], "properties": { "approved_cost_sec": { "type": "integer", "minimum": 0, "description": "The cost_sec explicitly approved by the person after a fresh quote." } } }
        ]
      },
      "Job": {
        "type": "object", "additionalProperties": true,
        "properties": { "id": { "type": "string", "format": "uuid" }, "status": { "type": "string" }, "cost_sec": { "type": "integer", "minimum": 0 } }
      },
      "ClipResult": {
        "type": "object", "additionalProperties": true,
        "properties": { "storage_key": { "type": "string" }, "thumbnail_key": { "type": "string" }, "score": { "type": "number" }, "transcript": { "type": "string" } }
      }
    }
  }
}
