Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Framefetch Net Extract API
version: 1.7.0
contact:
name: FrameFetch
url: https://framefetch.net
termsOfService: https://framefetch.net/impressum
description: 'Operations tagged Extract across 2 of this provider''s published API definitions: openapi.json, framefetch-net-openapi.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://framefetch.net
security:
- bearerAuth: []
tags:
- name: Extract
paths:
/v1/extract:
post:
operationId: extract
summary: Extract data from a video URL
parameters:
- $ref: '#/components/parameters/FormatMd'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ExtractRequest'
responses:
'200':
description: 'Extraction result. Returned as Markdown (Content-Type: text/plain) instead of JSON when ?format=md is set or an Accept: text/plain / text/markdown header is sent; returned as SubRip/WebVTT (Content-Type: application/x-subrip / text/vtt) when ?format=srt|vtt is set.'
content:
application/json:
schema:
$ref: '#/components/schemas/ExtractResult'
text/plain:
schema:
type: string
description: Markdown rendering of the ExtractResult.
application/x-subrip:
schema:
type: string
description: SubRip (.srt) rendering of the transcript's timed segments (?format=srt).
text/vtt:
schema:
type: string
description: WebVTT (.vtt) rendering of the transcript's timed segments (?format=vtt).
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Error'
'402':
$ref: '#/components/responses/Error'
'409':
description: SUBTITLES_UNAVAILABLE — ?format=srt|vtt was requested but the (possibly cached) result carries no timed transcript segments to build subtitles from.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
$ref: '#/components/responses/Error'
tags:
- Extract
servers:
- url: https://framefetch.net
components:
schemas:
Error:
type: object
properties:
error:
type: object
properties:
code:
type: string
message:
type: string
hint:
type: string
VideoComments:
type: object
description: 'Up to cap_applied TOP-LEVEL comments for a video, sorted by the platform''s "top" ranking (present only when "comments" was requested). Supported on YouTube only (via yt-dlp''s info-json comments). Reddit''s own public comment source — the unauthenticated thread JSON API — worked the same way until Reddit deprecated unauthenticated access to it on 2026-05-28; TikTok/Instagram/Pinterest never had a reliable public comment source. Billed a flat $0.0045 per call (independent of comments_cap), charged only when comments were actually produced. Best-effort: any fetch/parse failure, or an unsupported platform, omits the whole field with a warning and is NOT charged — never surfaces as an HTTP error code. Worker-internal failure codes behind those warnings (never an HTTP error, only a warnings[] string): COMMENTS_UNSUPPORTED (the platform has no comment source — drop "comments" from fields for it; see GET /v1/platforms), COMMENTS_PROVIDER_ERROR (the yt-dlp info-json fetch or parse failed).'
properties:
items:
type: array
items:
$ref: '#/components/schemas/VideoComment'
total_fetched:
type: integer
description: How many comments actually came back (after the cap).
cap_applied:
type: integer
description: The effective cap that was applied (default 30, hard max 200 via comments_cap).
sort:
type: string
enum:
- top
CommentSentiment:
type: object
description: 'Aggregated LLM sentiment over a video''s comments (present only when "comment_sentiment" was requested, which auto-includes "comments" as its input): ONE Groq chat call (llama-3.3-70b-versatile, temperature 0, JSON mode) reads up to 100 comments and returns the audience mood AS A WHOLE — not per-comment scoring. Requires at least 5 fetched comments; fewer (or a model/parse failure) omits the whole field with a warning and is NOT charged — never surfaces as an HTTP error code. Billed a flat $0.006 per call, on top of the $0.0045 "comments" call it rides on. No competitor offers this rollup.'
properties:
positive_pct:
type: number
description: 0-100; positive_pct + neutral_pct + negative_pct === 100 (normalized).
neutral_pct:
type: number
negative_pct:
type: number
summary:
type: string
description: 2-3 plain English sentences characterizing the overall audience reaction.
top_themes:
type: array
items:
type: string
description: Up to 5 short recurring themes across the comments.
representative:
type: object
description: One exemplar comment per polarity, when found.
properties:
positive:
type: string
negative:
type: string
ExtractRequest:
type: object
required:
- url
properties:
url:
type: string
format: uri
fields:
type: array
items:
type: string
enum:
- metadata
- insights
- transcript
- frames
- text_overlay
- digest
- audio_digest
- structured
- comments
- comment_sentiment
- delta
default:
- metadata
description: 'digest (LLM summary of the transcript) auto-includes transcript and degrades gracefully if none is available. audio_digest (a spoken mp3 briefing synthesized from the digest gist) auto-includes digest (and thus transcript); it returns a time-limited signed URL plus the voice/provider used, is billed per estimated spoken second only when the mp3 is actually produced, and degrades to a warning (never charged, never a hard error) when no TTS provider is configured or the requested language isn''t supported by the only configured provider. When translate is also set, the digest is generated directly in that target language so the audio speaks it too — combine audio_digest with translate for a foreign-language briefing from one call. structured (typed JSON understanding of the video: chapters, entities, products_shown, claims, key_moments) auto-includes transcript and also triggers the engine''s own keyframe pass over the source video, so it works even on caption-less sources; a single gpt-4o-mini vision call over the transcript plus up to 12 sampled keyframes, run at temperature 0 so the result is deterministic and cacheable; a flat $0.03 per video, charged only when the analysis is actually produced (a download/VLM/parse failure omits the field with a warning and is NOT charged — see StructuredError codes STRUCTURED_UNAVAILABLE, STRUCTURED_PROVIDER_ERROR, STRUCTURED_PARSE_ERROR on VideoStructured below). comments returns up to comments_cap top-level comments per video ({ items: [{ text, author?, like_count?, reply_count? }], total_fetched, cap_applied, sort:"top" }); it is INDEPENDENT of the transcript (no download) and available on YouTube only — Reddit''s unauthenticated comment API was deprecated on 2026-05-28 and now returns a hard 403, so Reddit omits the field exactly like TikTok/Instagram/Pinterest, with a warning and never a charge (see GET /v1/platforms and COMMENTS_UNSUPPORTED on VideoComments below). Author is a display handle only (no user ids/urls). Flat $0.0045 per call, charged only when comments were actually produced. comment_sentiment (aggregated audience sentiment: percentages, themes, summary — ONE LLM pass over the comments) auto-includes comments (its input; same YouTube-only availability, Reddit''s source having been deprecated) and needs at least 5 fetched comments — fewer, or a model/parse failure, omits it with a warning and is never charged (see CommentSentiment below). Flat $0.006 per call, on top of the comments call it rides on. No competitor offers this rollup. delta (change vs the most-recent prior fetch of the same url: engagement velocity per hour + title/thumbnail-change flags) is never cached and returns hasPrior:false the first time a url is seen. text_overlay (on-screen text via OCR) requires frames to also be requested, with a frames spec. Auto-skipped with a warning above 200 estimated frames.'
frames:
$ref: '#/components/schemas/FrameSpec'
verbosity:
type: string
translate:
type: string
description: 'Optional ISO-639-1 target language code (e.g. "es", "de", "ja") to also translate the transcript into, surfaced as transcript_translated (with timings preserved when the source transcript had them). Requires "transcript" in fields (or "digest", which pulls transcript in). Billed per audio-minute like transcription — even for caption-sourced transcripts, since the translation is a separate LLM pass. Supported: en, es, de, fr, pt, it, nl, pl, tr, ru, uk, ar, hi, id, vi, th, ja, ko, zh, sv, da, no, fi, cs, ro (25 languages).'
voice:
type: string
enum:
- alloy
- echo
- fable
- onyx
- nova
- shimmer
- Fritz-PlayAI
default: alloy
description: 'Spoken voice for the "audio_digest" field: the six OpenAI gpt-4o-mini-tts voices (alloy, echo, fable, onyx, nova, shimmer — multilingual) plus Groq/PlayAI''s "Fritz-PlayAI" (English/Arabic-only fallback, used when no OpenAI key is configured). Defaults to "alloy". Inert unless "audio_digest" is requested; an unlisted value is a typed 400 that echoes this allowlist.'
comments_cap:
type: integer
minimum: 1
maximum: 200
default: 30
description: Max number of top-level comments the "comments" field returns (1–200, default 30). Inert unless "comments" (or "comment_sentiment", which auto-includes it) is requested; a value outside 1–200 is a 400 INVALID_PARAMS.
publish:
type: boolean
default: false
description: Opt in to a public, shareable per-video SEO page at /v/<id> (a stable hash of the url). Publishes ONLY public metadata plus a short fair-use transcript excerpt — never the full transcript or a signed frame URL. Pages carry VideoObject structured data and are listed in /sitemap-videos.xml. Best-effort; never affects the API response.
TranscriptSegment:
type: object
description: 'One timed transcript segment: start/end offsets in seconds (floats, relative to the video start) and the segment''s text.'
required:
- start
- end
- text
properties:
start:
type: number
end:
type: number
text:
type: string
VideoStructured:
type: object
description: 'Typed, structured understanding of a video (present only when "structured" was requested): the transcript plus up to 12 sampled keyframes are run through a vision LLM (gpt-4o-mini, temperature 0) that returns chapters, entities, products shown, claims, and key moments as strict JSON. Deterministic and cacheable — unlike audio_digest it carries no expiring signed URL, so it takes part in the normal content-hash cache. Best-effort like digest: any download/VLM/parse failure omits the whole field, adds a warning to the top-level warnings array, and is NOT charged; the rest of the extraction stays usable. Worker-internal failure codes (never surfaced as an HTTP error, only as a warnings[] string): STRUCTURED_UNAVAILABLE (no vision provider configured — OPENAI_API_KEY unset), STRUCTURED_PROVIDER_ERROR (the provider returned a non-2xx response), STRUCTURED_PARSE_ERROR (the model''s output could not be parsed/validated into this schema). Billed a flat $0.03 per video (perStructuredVideoMicros, floor-pinned — see /pricing) charged only when the analysis is actually produced.'
properties:
chapters:
type: array
description: Timespan segments of the video.
items:
type: object
properties:
start_sec:
type: number
end_sec:
type: number
title:
type: string
summary:
type: string
entities:
type: array
description: People/orgs/places/products/other the model saw or heard.
items:
type: object
properties:
name:
type: string
type:
type: string
enum:
- person
- org
- place
- product
- other
products_shown:
type: array
description: Products/brands the video shows and/or talks about.
items:
type: object
properties:
name:
type: string
evidence:
type: string
enum:
- visual
- spoken
- both
description: 'How the product was surfaced: only on-screen, only mentioned, or both.'
claims:
type: array
description: Factual claims made in the video, optionally anchored to a timestamp.
items:
type: object
properties:
text:
type: string
timestamp_sec:
type: number
key_moments:
type: array
description: Notable moments (hooks, reveals, demo steps) at a specific second.
items:
type: object
properties:
timestamp_sec:
type: number
description:
type: string
keyframe_mode:
type: string
enum:
- scene
- uniform
description: 'How the analyzed frames were selected: ''scene'' = content-aware cut detection, ''uniform'' = even-sampling fallback for static/short clips with too few detected cuts.'
ExtractResult:
type: object
properties:
platform:
type: string
url:
type: string
metadata:
type: object
properties:
title:
type:
- string
- 'null'
uploader:
type:
- string
- 'null'
durationSec:
type:
- number
- 'null'
uploadDate:
type:
- string
- 'null'
description: normalized YYYY-MM-DD
sourceFps:
type:
- number
- 'null'
thumbnail:
type:
- string
- 'null'
description: poster/cover image URL (additive; may be null or omitted for older callers).
insights:
type: object
transcript:
type: object
properties:
text:
type: string
source:
type: string
lang:
type: string
description: Best-effort source language code, when known.
segments:
type: array
description: 'Timed transcript segments (Whisper verbose_json or caption cue timings). Additive: omitted entirely (never []) when the source carries no usable timings.'
items:
$ref: '#/components/schemas/TranscriptSegment'
transcript_translated:
type: object
description: Present only when the caller passed the `translate` param. An LLM translation of the transcript into the requested ISO-639-1 language, billed per audio-minute like transcription.
properties:
lang:
type: string
description: The requested ISO-639-1 target language.
text:
type: string
segments:
type: array
description: Mirrors the source cue timings (start/end unchanged, text translated); omitted when the source transcript carried no timings — never back-filled from the untranslated transcript.
items:
$ref: '#/components/schemas/TranscriptSegment'
digest:
type: object
description: LLM summary of the transcript (present only when the digest field was requested and a transcript was available).
properties:
gist:
type: string
topics:
type: array
items:
type: string
state:
type: object
audio_digest:
$ref: '#/components/schemas/AudioDigest'
structured:
$ref: '#/components/schemas/VideoStructured'
comments:
$ref: '#/components/schemas/VideoComments'
comment_sentiment:
$ref: '#/components/schemas/CommentSentiment'
delta:
type: object
description: 'Change since the most-recent prior fetch of the same url (present only when the delta field was requested). First time a url is seen -> { hasPrior: false } and nothing else. A per-hour rate is null when a counter was unavailable or no time had elapsed.'
properties:
hasPrior:
type: boolean
sinceTs:
type: integer
description: epoch ms of the prior snapshot this delta is measured against
viewsPerHr:
type:
- number
- 'null'
likesPerHr:
type:
- number
- 'null'
commentsPerHr:
type:
- number
- 'null'
titleChanged:
type: boolean
thumbnailChanged:
type: boolean
frames:
type: array
items:
type: object
properties:
index:
type: integer
url:
type: string
tSec:
type: number
textOverlay:
type: array
description: On-screen text per frame (OCR), same index as frames.
items:
type: object
properties:
index:
type: integer
text:
type: string
lines:
type: array
items:
type: object
properties:
text:
type: string
confidence:
type: number
bbox:
type: array
items:
type: number
minItems: 4
maxItems: 4
cost:
type: object
properties:
totalMicros:
type: integer
breakdownMicros:
type: object
VideoComment:
type: object
description: 'One top-level comment. Data-minimized by design: only the comment text, the author''s DISPLAY handle (never a user id, profile URL, or avatar), the like count, and the number of direct replies — all optional except text, omitted when the source doesn''t provide them.'
required:
- text
properties:
text:
type: string
author:
type: string
description: Display handle only — no user id / profile URL (data minimization).
like_count:
type: integer
reply_count:
type: integer
description: Number of direct replies to this top-level comment.
AudioDigest:
type: object
description: 'A spoken-audio rendering of the video''s digest (present only when "audio_digest" was requested and a digest was available to synthesize). Never served from the content-hash cache — its signed URL expires and the voice can vary per request. Billed per estimated spoken second (from the digest text length, ~15 chars/s) ONLY when the mp3 is actually produced; a TTS/upload failure omits this field, adds a warning to the top-level warnings array, and is not charged — the rest of the extraction stays usable. Failure reasons include no TTS provider configured (analogous to the worker-internal TTS_UNAVAILABLE code: neither OPENAI_API_KEY nor GROQ_API_KEY set) and an unsupported language on the only configured provider (TTS_LANG_UNSUPPORTED: Groq/PlayAI only speaks English/Arabic — set an OpenAI key for other languages); neither surfaces as an HTTP error code, only as a warnings[] string.'
properties:
url:
type: string
description: Signed URL to the mp3 (24h TTL).
seconds:
type: number
description: Estimated spoken duration (digest gist chars / 15), the billing basis — not measured from the mp3.
voice:
type: string
description: The TTS voice actually used (after per-provider fallback/mapping).
provider:
type: string
enum:
- openai
- groq
lang:
type: string
description: ISO-639-1 language the audio is spoken in; present only when a translate target drove a target-language digest.
FrameSpec:
type: object
properties:
mode:
type: string
enum:
- all
- every_n
- fps
- range
n:
type: integer
minimum: 1
fps:
type: number
maximum: 60
from:
type: number
to:
type: number
format:
type: string
enum:
- jpg
- png
- webp
width:
type: integer
minimum: 16
maximum: 7680
responses:
Error:
description: Error envelope
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
parameters:
FormatMd:
name: format
in: query
required: false
schema:
type: string
enum:
- md
- markdown
- srt
- vtt
description: 'Render the response in an alternate view instead of JSON. ''md''/''markdown'' returns a Markdown rendering (Content-Type: text/plain), equivalent to sending an Accept: text/plain or text/markdown header. ''srt''/''vtt'' renders the transcript''s timed segments as subtitles (Content-Type: application/x-subrip or text/vtt) — built from the TRANSLATED segments when the translate param was set, otherwise the source transcript''s segments; this is purely an egress view, so it reuses the same cached ExtractResult, cost, and cache key as the plain JSON call. Opt-in only (never inferred from an ambiguous Accept header); JSON stays the default. Extract-family endpoints only (/v1/extract, /v1/metadata, /v1/transcript, /v1/frames); srt/vtt additionally require "transcript" in fields (or "digest", which pulls it in) and return 409 SUBTITLES_UNAVAILABLE if the result has no timed segments (e.g. an older cached entry).'
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'API key from POST /v1/keys, sent as ''Authorization: Bearer <key>''.'
x-refined-from:
- openapi.json
- framefetch-net-openapi.json