{
  "openapi": "3.0.1",
  "info": {
    "title": "Video → LLM Analyzer",
    "description": "Drop a video, give it a prompt, and have an LLM analyze it. Results land in your Notion page so Claude Code can pick up where you left off. Uses Apify MCP Connectors to write to Notion on your behalf — no Notion API key required.",
    "version": "2.7",
    "x-build-id": "YYxA7rNL0eh7hsfTc"
  },
  "servers": [
    {
      "url": "https://api.apify.com/v2"
    }
  ],
  "paths": {
    "/acts/grizzlygriff~video-llm-analyzer/run-sync-get-dataset-items": {
      "post": {
        "operationId": "run-sync-get-dataset-items-grizzlygriff-video-llm-analyzer",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/acts/grizzlygriff~video-llm-analyzer/runs": {
      "post": {
        "operationId": "runs-sync-grizzlygriff-video-llm-analyzer",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor and returns information about the initiated run in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/runsResponseSchema"
                }
              }
            }
          }
        }
      }
    },
    "/acts/grizzlygriff~video-llm-analyzer/run-sync": {
      "post": {
        "operationId": "run-sync-grizzlygriff-video-llm-analyzer",
        "x-openai-isConsequential": false,
        "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
        "tags": [
          "Run Actor"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/inputSchema"
              }
            }
          }
        },
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Enter your Apify token here"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "inputSchema": {
        "type": "object",
        "required": [
          "video",
          "prompt"
        ],
        "properties": {
          "video": {
            "title": "Video",
            "type": "string",
            "description": "**New here?** Drop a video (or paste a link) here and keep the example prompt. To run with no setup, turn on **'Just give me the analysis'** in step 3 below, then click **Start**.\n\n**Easiest path: drop a file** (MP4/MOV/WebM, ≤1 GB, ≤10 min). Any video file works regardless of where it came from.\n\n**Or paste a URL from one of these platforms:** YouTube + Shorts, TikTok, X (Twitter), Instagram.\n\nAnything else — Vimeo, Dailymotion, Facebook, Reddit, Twitch, Bilibili, VK, and any other site not listed above — needs to be downloaded and uploaded as a file. We can't reliably download from these from the cloud yet. If you paste an unsupported URL, we'll tell you in about a second — before any compute runs, so you're not charged."
          },
          "prompt": {
            "title": "What should the LLM do with this video?",
            "minLength": 5,
            "maxLength": 4000,
            "type": "string",
            "description": "Plain English. *\"Summarize the main points\"*, *\"List every product mentioned with timestamps\"*, *\"Describe each scene and who's speaking.\"* The more specific, the better the result.\n\n**For YouTube links:** a visual prompt — one that mentions scenes, frames, what's *on screen*, who appears, etc. — automatically pulls real video frames (not just the thumbnail) **as long as the clip is 10 minutes or shorter**. For longer YouTube videos we can't download frames, so a visual prompt falls back to the transcript + thumbnail (the run says so in `softErrors` and the log); to force full-frame analysis on a long video, download it and upload the file instead. Purely spoken-word prompts always use the faster, cheaper transcript path."
          },
          "llmProvider": {
            "title": "Pick which AI watches the video",
            "enum": [
              "anthropic",
              "openai",
              "gemini"
            ],
            "type": "string",
            "description": "Which model family analyzes the video. **No API keys needed** — this Actor routes through Apify's hosted OpenRouter integration, so you only pay your Apify account at OpenRouter's actual cost (rounded to the nearest $0.00001). Pick the family here; for a specific model override, see the Model field below.",
            "default": "anthropic"
          },
          "model": {
            "title": "Model",
            "enum": [
              "default",
              "anthropic/claude-sonnet-4.5",
              "anthropic/claude-opus-4",
              "anthropic/claude-haiku-4.5",
              "openai/gpt-4o",
              "openai/gpt-4o-mini",
              "openai/gpt-5",
              "google/gemini-2.5-flash-lite",
              "google/gemini-2.5-flash",
              "google/gemini-2.5-pro"
            ],
            "type": "string",
            "description": "Pick **Default** to use the provider's standard model — Claude Sonnet 4.5, GPT-4o mini, or Gemini 2.5 Flash, depending on the provider above. Or pick a specific model. ⚠️ **If your model is from a different family than the provider above** (e.g. provider=Claude + model=`openai/gpt-4o`), the model's family wins and the run bills through that path. Keep the model family matching the provider, or leave on Default.",
            "default": "default"
          },
          "anthropicApiKey": {
            "title": "Anthropic API key (optional — for direct billing)",
            "type": "string",
            "description": "**Only used when the provider above is Claude. Ignored for GPT and Gemini.** Skip this unless you want to spend your own Anthropic credits instead of Apify's hosted OpenRouter (the default).\n\nWhen filled AND the provider is Claude, the call hits Anthropic directly with your key — useful for Anthropic-specific features (prompt caching, extended thinking) or to bill your own Anthropic account instead of Apify's OpenRouter passthrough. The key lives in this Actor's memory for the run and is then discarded. Get a key at [console.anthropic.com/settings/keys](https://console.anthropic.com/settings/keys)."
          },
          "whisperKey": {
            "title": "Whisper key for audio transcription (optional)",
            "type": "string",
            "description": "**Skip this — most prompts don't need spoken words.** Frames + the video's metadata cover ~90% of analysis prompts. Add a Whisper key (paste an OpenAI key from [platform.openai.com/api-keys](https://platform.openai.com/api-keys)) only when the analysis genuinely depends on what people *said*. ~$0.006 per minute of audio.\n\nThis is the one piece that still needs a raw API key for now — no MCP server wraps Whisper yet."
          },
          "skipDestination": {
            "title": "Just give me the analysis — no connection needed",
            "type": "boolean",
            "description": "**Turn this on to get the analysis back without connecting anything** — recommended for your first run, when you don't have a Notion / Slack / Sheets connection yet, or when calling from an agent / API / Claude Code. **Leaving the destination below empty without turning this on will stop the run and ask for a destination.** When on, the destination + page/channel/sheet + write-behavior fields below are ignored and the analysis lands in this run's dataset row and OUTPUT record.",
            "default": false
          },
          "destination": {
            "title": "Auto-save results to Notion / Slack / Sheets (optional)",
            "type": "string",
            "description": "**Optional. To run with no connection, turn on 'Just give me the analysis' above and leave this empty.** Otherwise, pick a saved connection to auto-write results into **Notion** (fully tested), **Slack**, or **Google Sheets** (both functional, lighter-tested).\n\nDon't have a connection? Click the field → **Create new** → log in to the app and authorize. Seeing Connectors tagged **'Not eligible'**? Those are for other services (GitHub, Supabase, Sentry…) that don't write to Notion/Slack/Sheets — picking one is safe, we catch it before the LLM call and exit with **no LLM cost incurred**."
          },
          "destinationTarget": {
            "title": "Page, channel, or sheet",
            "type": "string",
            "description": "Where exactly within the connection. Format depends on what you picked:\n\n| If you picked… | Paste this | Example |\n|---|---|---|\n| **Notion** | Page URL (not a database) | `https://www.notion.so/My-Notes-1234abcd5678` |\n| **Slack** | Channel name or ID | `#video-notes` or `C012AB3CD` |\n| **Google Sheets** | Spreadsheet URL | `https://docs.google.com/spreadsheets/d/<id>/edit` |\n\nSlack needs the Apify Slack app in the channel. Notion appends a section by default, or creates a new child page if your prompt asks for one. Sheets always appends a row."
          },
          "writeBehavior": {
            "title": "How to write to the destination",
            "enum": [
              "auto",
              "append",
              "nested"
            ],
            "type": "string",
            "description": "**Default (Smart) is right for nearly everyone.** Reads your prompt — if you said *\"new page\"* / *\"separate section\"*, creates a new container; otherwise appends to what's there. Pin one of the others if you want to override prompt-detection.",
            "default": "auto"
          },
          "framesToExtract": {
            "title": "Frames to extract",
            "minimum": 0,
            "maximum": 30,
            "type": "integer",
            "description": "Leave at 0 for adaptive (~1 frame per 5s, between 4 and 12 frames). Override only if you have a specific reason — more frames = higher LLM cost. **Note:** very high frame counts can produce a payload too large for the LLM to accept; if you set this higher than 12 the Actor will cap it at 12 and log a notice.\n\n**YouTube tip:** captioned YouTube videos are normally read via transcript + a single thumbnail (cheapest). Setting this above 0 — or writing a visual prompt (see the **prompt** field above) — makes the Actor download the video and extract real frames instead, as long as the clip is within the 10-minute limit. On a captioned video this keeps the captions too, so you get frames **and** transcript.",
            "default": 0
          },
          "forceFreshRun": {
            "title": "Force a fresh run (skip duplicate-result shortcut)",
            "type": "boolean",
            "description": "By default, if you re-run with the **exact same** prompt + video + destination + behaviour as a previous run, the Actor replays the previous result instead of paying for the LLM call again. Set this to `true` to force a fresh analysis — useful if the source video has been re-uploaded under the same URL or you want a different angle on the same clip.",
            "default": false
          },
          "maxChargeUsd": {
            "title": "Maximum cost for this run (USD)",
            "minimum": 0,
            "type": "number",
            "description": "**Set a ceiling on the LLM + Whisper cost of this run.** When the Actor finishes ingest and knows the real frame/transcript size, it computes a worst-case LLM+Whisper cost. If that estimate exceeds this cap, the run aborts *before* the LLM call fires — no LLM cost incurred. **This caps only the LLM/Whisper portion** — Apify compute and the flat $0.015 per-run fee are billed on top (see the README pricing table). Leave at 0 for no cap. As a guide, the LLM+Whisper portion is under $0.05 for most captioned YouTube videos and tops out around $0.15 for a 10-min upload on Claude Sonnet. Set $0.20 for headroom on typical videos; $0.50 for long uploads.",
            "default": 0
          }
        }
      },
      "runsResponseSchema": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "actId": {
                "type": "string"
              },
              "userId": {
                "type": "string"
              },
              "startedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "finishedAt": {
                "type": "string",
                "format": "date-time",
                "example": "2025-01-08T00:00:00.000Z"
              },
              "status": {
                "type": "string",
                "example": "READY"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "origin": {
                    "type": "string",
                    "example": "API"
                  },
                  "userAgent": {
                    "type": "string"
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "inputBodyLen": {
                    "type": "integer",
                    "example": 2000
                  },
                  "rebootCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "restartCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "resurrectCount": {
                    "type": "integer",
                    "example": 0
                  },
                  "computeUnits": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "options": {
                "type": "object",
                "properties": {
                  "build": {
                    "type": "string",
                    "example": "latest"
                  },
                  "timeoutSecs": {
                    "type": "integer",
                    "example": 300
                  },
                  "memoryMbytes": {
                    "type": "integer",
                    "example": 1024
                  },
                  "diskMbytes": {
                    "type": "integer",
                    "example": 2048
                  }
                }
              },
              "buildId": {
                "type": "string"
              },
              "defaultKeyValueStoreId": {
                "type": "string"
              },
              "defaultDatasetId": {
                "type": "string"
              },
              "defaultRequestQueueId": {
                "type": "string"
              },
              "buildNumber": {
                "type": "string",
                "example": "1.0.0"
              },
              "containerUrl": {
                "type": "string"
              },
              "usage": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "integer",
                    "example": 1
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              },
              "usageTotalUsd": {
                "type": "number",
                "example": 0.00005
              },
              "usageUsd": {
                "type": "object",
                "properties": {
                  "ACTOR_COMPUTE_UNITS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATASET_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "KEY_VALUE_STORE_WRITES": {
                    "type": "number",
                    "example": 0.00005
                  },
                  "KEY_VALUE_STORE_LISTS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_READS": {
                    "type": "integer",
                    "example": 0
                  },
                  "REQUEST_QUEUE_WRITES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_INTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "DATA_TRANSFER_EXTERNAL_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                    "type": "integer",
                    "example": 0
                  },
                  "PROXY_SERPS": {
                    "type": "integer",
                    "example": 0
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}