Properties
| Name | Type | Description |
|---|---|---|
| platform | string | |
| url | string | |
| metadata | object | |
| insights | object | |
| transcript | object | |
| transcript_translated | object | 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. |
| digest | object | LLM summary of the transcript (present only when the digest field was requested and a transcript was available). |
| audio_digest | object | |
| structured | object | |
| comments | object | |
| comment_sentiment | object | |
| delta | object | 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 wh |
| frames | array | |
| textOverlay | array | On-screen text per frame (OCR), same index as frames. |
| cost | object |
JSON Schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://artifacts.apis.io/framefetch-net/json-schema/framefetch-net-extract-result-schema.json",
"title": "ExtractResult",
"x-generated": "2026-10-30",
"x-method": "derived",
"x-generator": "derive-json-schema.py",
"x-source": "openapi/framefetch-net-openapi.json#/components/schemas/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": "#/$defs/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": "#/$defs/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": "#/$defs/AudioDigest"
},
"structured": {
"$ref": "#/$defs/VideoStructured"
},
"comments": {
"$ref": "#/$defs/VideoComments"
},
"comment_sentiment": {
"$ref": "#/$defs/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"
}
}
}
},
"$defs": {
"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."
}
}
},
"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"
}
}
}
}
},
"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"
}
}
},
"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."
}
}
},
"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": "#/$defs/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"
]
}
}
},
"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."
}
}
}
}
}
Work with this as data
Every JSON Schema here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for schemas
4 MCP tools reach this
find_json_schemasBrowse and filter every JSON Schema in the catalog.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.
Call it yourself
curl for this page
This JSON Schema
curl "https://apis.io/api/v1/json-schemas/framefetch-net-extract-result"
All schemas
curl "https://apis.io/api/v1/json-schemas?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.