# Analyze Uploaded File Source: https://docs.vidnavigator.com/api-reference/endpoint/analyze-file POST /analyze/file Analyze an uploaded file and return comprehensive analysis results with intelligent caching. **Behavior:** - If no query provided: Returns summary analysis (cached if available) - If query provided: Returns both summary (from cache if available) and fresh question analysis **Billing:** `analysis_request` is token-based — `quantity = ceil(total_tokens / 15000)`. One unit is charged up front as a credit gate; additional units are topped up at the end if the LLM call exceeded 15,000 tokens. The file's transcript is read locally from your storage — no proxy fetch is involved. Set `include_usage=true` to receive the per-charge breakdown. Optional: set `transcript_text=true` to return the transcript as a single text string instead of an array of segments. Run AI-powered analysis on a previously uploaded audio/video file. ## Overview Similar to **Analyze Video**, but operates on files in your library. ## Request Body * `file_id` (string, required): ID of the uploaded file to analyze * `query` (string, optional): Ask a specific question about the content ## Billing File analysis consumes `analysis_request` units based on context size. One unit covers up to **15,000 total tokens**, and longer contexts are billed with `ceil(total_tokens / 15000)`. For example, an analysis that uses 17,000 total tokens consumes 2 `analysis_request` units. One unit is charged up front as a credit gate; additional units are topped up at the end if the LLM call exceeded 15,000 tokens. The file's transcript is read locally from your storage — no proxy fetch is involved, so no `standard_request` / `residential_request` is charged. Set `include_usage: true` to receive a [`usage`](/api-reference/usage-costs#per-call-usage-disclosure) block with the per-charge breakdown. ## Example Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/analyze/file" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_id": "file_abc123", "query": "Summarize the key points" }' ``` # Analyze Video Source: https://docs.vidnavigator.com/api-reference/endpoint/analyze-video POST /analyze/video Analyze an online video and return comprehensive analysis results with intelligent caching. **Behavior:** - If no query provided: Returns summary analysis (cached if available) - If query provided: Returns both summary (from cache if available) and fresh question analysis **Caching:** - Summary analyses are cached and reused - Question/query analyses are not cached (always fresh) - Transcripts are cached for optimization **Billing:** `analysis_request` is token-based — `quantity = ceil(total_tokens / 15000)`. One unit is charged up front as a credit gate; additional units are topped up at the end if the LLM call exceeded 15,000 tokens. When the transcript isn't cached, one additional `residential_request` or `standard_request` is charged for the fetch (residential for YouTube/Instagram/Facebook-watch/Rumble, standard otherwise). Set `include_usage=true` to receive the per-charge breakdown — the consolidated `analysis_request` entry carries a nested `tokens` object with `prompt_tokens`/`completion_tokens`/`total_tokens`. Optional: set `transcript_text=true` to return the transcript as a single text string instead of an array of segments. Get comprehensive AI-powered analysis of online videos including summaries, key insights, and structured data extraction. ## Overview Transform any online video into structured insights with our advanced AI analysis. Extract summaries, identify people and places, generate key topics, and ask specific questions about the content. ### Analysis Capabilities AI-generated summaries highlighting key points and themes Identify people, places, organizations, and concepts mentioned Extract main topics, themes, and discussion points Ask specific questions about the video content ### Intelligent Caching * **Summary analyses** are cached and reused for efficiency * **Question/query analyses** are always fresh and never cached * **Transcripts** are cached to optimize performance ## Billing Analysis consumes `analysis_request` units based on context size. One unit covers up to **15,000 total tokens**, and longer contexts are billed with `ceil(total_tokens / 15000)`. For example, an analysis that uses 17,000 total tokens consumes 2 `analysis_request` units. One unit is charged up front as a credit gate; additional units are topped up at the end if the LLM call exceeded 15,000 tokens. When the transcript isn't cached, one additional `residential_request` or `standard_request` is charged for the fetch (residential for YouTube/Instagram/Facebook-watch/Rumble, standard otherwise). Set `include_usage: true` to receive a [`usage`](/api-reference/usage-costs#per-call-usage-disclosure) block with the per-charge breakdown — the consolidated `analysis_request` entry carries a nested `tokens` object with `prompt_tokens` / `completion_tokens` / `total_tokens`. ## Example Usage ### Basic Analysis (Summary Only) ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/analyze/video" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ" }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/analyze/video" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ" } response = requests.post(url, headers=headers, json=data) analysis = response.json() print("Summary:", analysis['data']['transcript_analysis']['summary']) print("Key Topics:", analysis['data']['transcript_analysis']['key_topics']) ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/analyze/video', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ' }) }); const analysis = await response.json(); ``` ### Analysis with Custom Question ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/analyze/video" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ", "query": "What is the main topic discussed and who are the key speakers?" }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/analyze/video" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ", "query": "What is the main topic discussed and who are the key speakers?" } response = requests.post(url, headers=headers, json=data) analysis = response.json() # Get both summary and question-specific analysis summary = analysis['data']['transcript_analysis']['summary'] question_answer = analysis['data']['transcript_analysis']['question_analysis'] ``` ## Response Example ### Basic Analysis Response ```json theme={null} { "status": "success", "data": { "video_info": { "title": "How to Build a Startup", "description": "A comprehensive guide to building a startup from scratch.", "thumbnail": "https://example.com/thumbnail.jpg", "url": "https://youtube.com/watch?v=example123", "channel": "TechTalks", "duration": 1530.0, "views": 150000, "likes": 4500, "published_date": "2023-05-10", "keywords": ["startup", "entrepreneurship", "business"], "category": "Education" }, "transcript": [ { "text": "Welcome to today's discussion on building startups...", "start": 0.0, "end": 4.2 } ], "transcript_analysis": { "summary": "This video covers essential startup building strategies including market validation, team building, and funding approaches. The speaker emphasizes the importance of customer feedback and iterative development.", "key_topics": [ "Market validation", "Team building", "Funding strategies", "Customer feedback", "Product development" ], "people": [ { "name": "Sarah Johnson", "context": "CEO and founder discussing her startup journey" } ], "places": [ { "name": "Silicon Valley", "context": "Mentioned as a startup hub" } ], "sentiment": "positive", "category": "business_education" } } } ``` ### Analysis with Question Response ```json theme={null} { "status": "success", "data": { "transcript_analysis": { "summary": "...", "question_analysis": { "question": "What is the main topic discussed and who are the key speakers?", "answer": "The main topic is startup building strategies with focus on market validation and customer development. The key speaker is Sarah Johnson, CEO of TechCorp, who shares insights from her 5-year entrepreneurial journey.", "confidence": 0.92, "relevant_segments": [ { "text": "Hi, I'm Sarah Johnson, CEO of TechCorp...", "start": 15.0, "end": 25.0 } ] } } } } ``` ## Use Cases Quickly understand video content without watching entire videos Extract structured data from educational or training content Analyze content for compliance and safety requirements Create summaries and study materials from video lectures # Cancel File Processing Source: https://docs.vidnavigator.com/api-reference/endpoint/cancel-file POST /file/{file_id}/cancel Cancel an ongoing file upload/processing Cancel an ongoing file upload or processing job. ## Path Parameter * `file_id` (string, required) ## Example Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/file/file_abc123/cancel" \ -H "X-API-Key: YOUR_API_KEY" ``` ## Success Response (200 OK) Returns a confirmation that the file processing has been cancelled. ```json theme={null} { "status": "success", "data": { "file_id": "file_abc123", "file_name": "processing_video.mp4", "message": "File cancelled successfully" } } ``` # Create Namespace Source: https://docs.vidnavigator.com/api-reference/endpoint/create-namespace POST /namespaces Create a new namespace for organizing files. Maximum 5 namespaces per user. The name 'default' is reserved and cannot be used. Create a new namespace to organize your uploaded files. ## Overview Each user can have up to **5 namespaces**. The name `default` is reserved and cannot be used when creating a new namespace. ## Example Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/namespaces" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Client Calls" }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/namespaces" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = {"name": "Client Calls"} response = requests.post(url, headers=headers, json=data) result = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/namespaces', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Client Calls' }) }); const result = await response.json(); ``` ## Success Response (201 Created) ```json theme={null} { "status": "success", "data": { "id": "64a1b2c3d4e5f6789abc0003", "name": "Client Calls", "created_at": "2025-03-20T09:15:00Z", "updated_at": "2025-03-20T09:15:00Z" } } ``` # Delete File Source: https://docs.vidnavigator.com/api-reference/endpoint/delete-file DELETE /file/{file_id}/delete Delete a file and its associated data from the database and cloud storage Delete a file and all associated data from storage. ## Path Parameter * `file_id` (string, required) ## Example Request ```bash cURL theme={null} curl -X DELETE "https://api.vidnavigator.com/v1/file/file_abc123/delete" \ -H "X-API-Key: YOUR_API_KEY" ``` ## Success Response (200 OK) Returns a confirmation that the file and its associated data have been successfully deleted. ```json theme={null} { "status": "success", "data": { "file_id": "file_abc123", "file_name": "video_to_delete.mp4", "message": "File deleted successfully" } } ``` # Delete Namespace Source: https://docs.vidnavigator.com/api-reference/endpoint/delete-namespace DELETE /namespaces/{namespace_id} Delete a namespace and remove its association from all files. The 'default' namespace cannot be deleted. Delete a namespace. The `default` namespace cannot be deleted. Files previously assigned to this namespace will have the association removed but are **not** deleted. ## Example Request ```bash cURL theme={null} curl -X DELETE "https://api.vidnavigator.com/v1/namespaces/64a1b2c3d4e5f6789abc0003" \ -H "X-API-Key: YOUR_API_KEY" ``` ```python Python theme={null} import requests namespace_id = "64a1b2c3d4e5f6789abc0003" url = f"https://api.vidnavigator.com/v1/namespaces/{namespace_id}" headers = {"X-API-Key": "YOUR_API_KEY"} response = requests.delete(url, headers=headers) result = response.json() ``` ```javascript JavaScript theme={null} const namespaceId = '64a1b2c3d4e5f6789abc0003'; const response = await fetch(`https://api.vidnavigator.com/v1/namespaces/${namespaceId}`, { method: 'DELETE', headers: { 'X-API-Key': 'YOUR_API_KEY' } }); const result = await response.json(); ``` ## Success Response ```json theme={null} { "status": "success", "message": "Namespace deleted successfully" } ``` # Extract Data from File Source: https://docs.vidnavigator.com/api-reference/endpoint/extract-file POST /extract/file Extract structured data from an uploaded file's transcript using a custom schema. Provide a `file_id` and a `schema` describing the fields to extract. The file must be processed and have a transcript available. Optionally include `what_to_extract` to guide the extraction. **Schema format:** Each field must have `type` and `description`. Supported types: `String`, `Number`, `Boolean`, `Integer`, `Object`, `Array`, `Enum`. Max 10 root fields, max 3 nesting levels. **Content-Type:** Accepts `application/json`, YAML (`application/yaml`, `application/x-yaml`, `text/yaml`), or `multipart/form-data`. For multipart requests, send `file_id` as a form field and `schema` as either a JSON/YAML form value or an uploaded JSON/YAML file. If the uploaded schema file contains a full request body with a nested `schema` property, the nested schema is used and the form `file_id` takes precedence. **Token usage:** Set `include_usage=true` to include prompt/completion token counts in the response. **Billing:** Each extraction consumes at least 1 analysis credit. For longer transcripts, billing scales as `ceil(total_tokens / 15000)` credits. All charges are reverted if the request fails. Extract structured data from an uploaded file's transcript using a custom schema you define. ## Overview Extract structured data from files you have previously uploaded to your VidNavigator library. The file must be fully processed and have a transcript available. ### How It Works 1. Provide a `file_id` and a `schema` describing the structured fields you want 2. VidNavigator reads the transcript from the uploaded file 3. VidNavigator runs AI extraction against your schema 4. You receive structured JSON matching your schema definition, plus `file_info` ### Schema Rules * Each field must have `type` and `description` * Supported types: `String`, `Number`, `Boolean`, `Integer`, `Object`, `Array`, `Enum` * Maximum **10 root-level fields** * Maximum **3 nesting levels** You can also send the request body as YAML by setting `Content-Type` to `application/x-yaml` or `text/yaml`. This endpoint does **not** support a `transcribe` parameter. It works only on files that already have a transcript available. ## Example Usage ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/extract/file" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_id": "file_abc123", "schema": { "action_items": { "type": "Array", "description": "Action items and tasks mentioned in the meeting", "items": { "type": "Object", "description": "An action item", "properties": { "task": { "type": "String", "description": "The task description" }, "assignee": { "type": "String", "description": "Person assigned" }, "deadline": { "type": "String", "description": "Deadline if mentioned" } } } }, "decisions_made": { "type": "Array", "description": "Key decisions made during the meeting", "items": { "type": "String", "description": "A decision" } } }, "what_to_extract": "Extract action items and deadlines from this meeting", "include_usage": true }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/extract/file" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "file_id": "file_abc123", "schema": { "action_items": { "type": "Array", "description": "Action items and tasks mentioned in the meeting", "items": { "type": "Object", "description": "An action item", "properties": { "task": {"type": "String", "description": "The task description"}, "assignee": {"type": "String", "description": "Person assigned"}, "deadline": {"type": "String", "description": "Deadline if mentioned"} } } }, "decisions_made": { "type": "Array", "description": "Key decisions made during the meeting", "items": {"type": "String", "description": "A decision"} } }, "what_to_extract": "Extract action items and deadlines from this meeting" } response = requests.post(url, headers=headers, json=data) result = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/extract/file', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ file_id: 'file_abc123', schema: { action_items: { type: 'Array', description: 'Action items and tasks mentioned in the meeting', items: { type: 'Object', description: 'An action item', properties: { task: { type: 'String', description: 'The task description' }, assignee: { type: 'String', description: 'Person assigned' }, deadline: { type: 'String', description: 'Deadline if mentioned' } } } }, decisions_made: { type: 'Array', description: 'Key decisions made during the meeting', items: { type: 'String', description: 'A decision' } } }, what_to_extract: 'Extract action items and deadlines from this meeting' }) }); const result = await response.json(); ``` ## Response Example ```json theme={null} { "status": "success", "data": { "action_items": [ { "task": "Update the product roadmap with Q2 priorities", "assignee": "Sarah", "deadline": "Friday" }, { "task": "Schedule customer interviews for user research", "assignee": "Mike", "deadline": "next week" } ], "decisions_made": [ "Proceed with option B for the pricing model", "Delay the mobile app launch to Q3" ] }, "file_info": { "id": "file_abc123", "name": "weekly-product-meeting.mp4", "size": 248517632, "type": "video/mp4", "duration": 3720.0, "status": "completed", "created_at": "2026-04-10T09:30:00Z", "updated_at": "2026-04-10T09:36:12Z", "has_transcript": true, "namespace_ids": ["ns_team_notes"], "namespaces": [ { "id": "ns_team_notes", "name": "Team Notes" } ] }, "usage": { "prompt_tokens": 3200, "completion_tokens": 120, "total_tokens": 3320 } } ``` The `usage` field is only included when `include_usage=true` in the request. `file_info` is included in the response metadata. ## Billing * AI extraction consumes `analysis_request` units in blocks of **15,000 total tokens** * Formula: `ceil(total_tokens / 15000)` * Examples: * `14,000` tokens -> `1` `analysis_request` unit * `17,000` tokens -> `2` `analysis_request` units * `31,000` tokens -> `3` `analysis_request` units * There is no speech-to-text billing on this endpoint * Failed requests are not charged * Set `include_usage: true` to receive a [`usage`](/api-reference/usage-costs#per-call-usage-disclosure) block with the per-charge breakdown (the `analysis_request` entry carries a nested `tokens` object) # Extract Data from Video Source: https://docs.vidnavigator.com/api-reference/endpoint/extract-video POST /extract/video Extract structured data from an online video's transcript using a custom schema. Provide a `video_url` and a `schema` describing the fields to extract. Optionally include `what_to_extract` to guide the extraction. **Auto-transcription:** For non-YouTube videos without an existing transcript (e.g. Instagram, TikTok, Facebook), the API automatically transcribes the video audio when `transcribe` is `true` (the default). This uses speech-to-text credits (`transcription_hour` usage). YouTube videos rely on platform captions and cannot be auto-transcribed. Set `transcribe=false` to disable this behavior. **Schema format:** Each field must have `type` and `description`. Supported types: `String`, `Number`, `Boolean`, `Integer`, `Object`, `Array`, `Enum`. Max 10 root fields, max 3 nesting levels. **Content-Type:** Accepts `application/json`, YAML (`application/yaml`, `application/x-yaml`, `text/yaml`), or `multipart/form-data`. For multipart requests, send `video_url` as a form field and `schema` as either a JSON/YAML form value or an uploaded JSON/YAML file (for example, `schema=@fact-check.yaml`). If the uploaded schema file contains a full request body with a nested `schema` property, the nested schema is used and the form `video_url` takes precedence. **Token usage:** Set `include_usage=true` to include prompt/completion token counts in the response. **Billing:** Each extraction consumes at least 1 analysis_request unit. For longer transcripts, billing scales as `ceil(total_tokens / 15000)` analysis_request units. If auto-transcription is triggered, transcription_hour usage is also charged based on video duration. All charges are reverted if the request fails. **Long videos:** this endpoint is synchronous and holds the connection open for the whole transcription. For media longer than ~10 minutes use `POST /extract/video/async` instead, which returns a `task_id` and can call your webhook when it finishes. Media longer than 10 minutes is rejected here with `video_too_long` (HTTP 400). The metadata fetch needed to read the video's duration is billed (`standard_request` or `residential_request`) and is **not** refunded: the limit is documented, so the proxy call was spent answering a request that could not be served. No `transcription_hour` is charged — no audio is processed. Send long media to the async endpoint, which has no cap. See https://docs.vidnavigator.com/guides/async-jobs Extract structured data from an online video's transcript using a custom schema you define. ## Overview The extraction endpoint lets you pull structured, typed data from any video transcript by providing a JSON schema describing the fields you need. This is ideal for building automated pipelines that need consistent, machine-readable output from video content. ### How It Works 1. Provide a `video_url` and a `schema` defining the fields to extract 2. VidNavigator fetches the platform transcript when available 3. For supported non-YouTube sources, it can auto-transcribe audio when no transcript exists 4. VidNavigator runs AI extraction against your schema 5. You receive structured JSON matching your schema definition, plus `video_info` ### Schema Rules * Each field must have `type` and `description` * Supported types: `String`, `Number`, `Boolean`, `Integer`, `Object`, `Array`, `Enum` * Maximum **10 root-level fields** * Maximum **3 nesting levels** You can also send the request body as YAML by setting `Content-Type` to `application/x-yaml` or `text/yaml`. ### Automatic Transcription The `transcribe` parameter controls whether VidNavigator should automatically fall back to speech-to-text when a transcript is not available. * `transcribe=true` by default * applies to non-YouTube videos only * useful for platforms like Instagram, TikTok, Facebook, X, and similar sources * YouTube extraction relies on platform captions and does not support speech-to-text fallback through this endpoint If you set `transcribe=false`, the request will fail when no transcript is available instead of triggering speech-to-text processing. ## Synchronous or async? The extraction itself is fast; the speech-to-text step in front of it is what takes time on long videos. This endpoint therefore comes in two modes, with the same request body and the same result: | Mode | Endpoint | Media duration (with speech-to-text) | How you get the result | | - | - | - | - | | Synchronous | `POST /extract/video` | **Up to 10 minutes** | In the HTTP response | | Async | [`POST /extract/video/async`](/api-reference/endpoint/extract-video-async) | **Any length** | Poll `GET /extract/video/{task_id}` or receive a [webhook](/webhooks) | **When `transcribe` is on and the video is longer than 10 minutes, the synchronous endpoint rejects it** with `400 video_too_long`. Use the [async mode](/api-reference/endpoint/extract-video-async) whenever the video may run past 10 minutes — it also works for short videos. ## Example Usage ### Basic Extraction ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/extract/video" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ", "schema": { "main_topics": { "type": "Array", "description": "List of main topics discussed", "items": { "type": "String", "description": "A topic" } }, "sentiment": { "type": "Enum", "description": "Overall sentiment of the video", "enum": ["positive", "negative", "neutral"] }, "key_takeaway": { "type": "String", "description": "The single most important takeaway" } } }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/extract/video" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ", "schema": { "main_topics": { "type": "Array", "description": "List of main topics discussed", "items": {"type": "String", "description": "A topic"} }, "sentiment": { "type": "Enum", "description": "Overall sentiment of the video", "enum": ["positive", "negative", "neutral"] }, "key_takeaway": { "type": "String", "description": "The single most important takeaway" } } } response = requests.post(url, headers=headers, json=data) result = response.json() print(result["data"]) ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/extract/video', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ', schema: { main_topics: { type: 'Array', description: 'List of main topics discussed', items: { type: 'String', description: 'A topic' } }, sentiment: { type: 'Enum', description: 'Overall sentiment of the video', enum: ['positive', 'negative', 'neutral'] }, key_takeaway: { type: 'String', description: 'The single most important takeaway' } } }) }); const result = await response.json(); ``` ### With Extraction Guidance Use `what_to_extract` to provide additional context to the AI about what to focus on: ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/extract/video" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ", "what_to_extract": "Focus on product names and pricing mentioned in the video", "schema": { "products": { "type": "Array", "description": "Products mentioned in the video", "items": { "type": "Object", "description": "A product", "properties": { "name": { "type": "String", "description": "Product name" }, "price": { "type": "String", "description": "Price if mentioned" } } } } }, "include_usage": true }' ``` ### Disable Auto-Transcription Use `transcribe=false` when you want extraction to run only if a platform transcript already exists: ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/extract/video" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.instagram.com/reel/example/", "transcribe": false, "schema": { "main_claim": { "type": "String", "description": "Main claim made in the video" } } }' ``` ## Response Example ```json theme={null} { "status": "success", "data": { "main_topics": ["machine learning", "neural networks", "data preprocessing"], "sentiment": "positive", "key_takeaway": "Start with clean data before choosing a model architecture" }, "video_info": { "title": "Machine Learning Fundamentals", "description": "An introduction to practical machine learning workflows.", "thumbnail": "https://example.com/thumbnail.jpg", "url": "https://youtube.com/watch?v=example123", "channel": "AI Academy", "duration": 4520.0, "views": 152340, "likes": 8100, "published_date": "2026-03-05", "keywords": ["machine learning", "ai", "data science"], "category": "Education" }, "usage": { "prompt_tokens": 2150, "completion_tokens": 85, "total_tokens": 2235 } } ``` The `usage` field is only included when `include_usage=true` in the request. `video_info` is included in the response metadata. ## Billing * AI extraction consumes `analysis_request` units in blocks of **15,000 total tokens** * Formula: `ceil(total_tokens / 15000)` * Examples: * `14,000` tokens -> `1` `analysis_request` unit * `17,000` tokens -> `2` `analysis_request` units * `31,000` tokens -> `3` `analysis_request` units * If `transcribe=true` triggers speech-to-text fallback, speech-to-text is charged separately as `transcription_hour` usage based on **video/audio duration** * Failed requests are not charged * Set `include_usage: true` to receive a [`usage`](/api-reference/usage-costs#per-call-usage-disclosure) block with the per-charge breakdown (the `analysis_request` entry carries a nested `tokens` object) * The async mode is billed exactly the same way * A `video_too_long` rejection on the synchronous endpoint still bills the metadata fetch that read the duration (`standard_request` or `residential_request`, not refunded), but no `transcription_hour` and no `analysis_request` ## Error Responses | Status | `error` | Description | | - | - | - | | `400` | `missing_parameter`, `request_body_required`, `invalid_schema`, `input_too_large` | Invalid request or schema. | | `400` | `video_too_long` | Speech-to-text is needed and the media is longer than 10 minutes. Use [`POST /extract/video/async`](/api-reference/endpoint/extract-video-async). | | `402` | `limit_exceeded` | Not enough credits. | | `404` | `video_not_found`, `video_unavailable`, `transcript_not_available` | The video can't be found or accessed, or no transcript is available. | | `500` | `extraction_failed`, `transcription_failed`, `internal_server_error` | Processing failed. Charges are reverted. | | `503` | `system_overload` | Temporarily overloaded. Retry later. | See [Errors](/api-reference/errors) for the full list. ## Use Cases Build automated pipelines that extract consistent structured data from video content at scale Automatically tag and categorize videos with custom taxonomies Extract product mentions, sentiment, and competitive insights from video reviews Pull specific compliance-relevant data points from training or policy videos # Extract Data from Video (Async) Source: https://docs.vidnavigator.com/api-reference/endpoint/extract-video-async POST /extract/video/async Queue a structured-data extraction and return a `task_id` immediately. **Use this instead of `POST /extract/video` whenever `transcribe` is left on and the video may run past ~10 minutes.** The extraction itself is fast; the speech-to-text step in front of it is what makes a synchronous request time out. This endpoint has no duration cap. Accepts the same body as `POST /extract/video` — JSON, YAML and `multipart/form-data` all work here too — plus `webhook_url`. The schema is validated at submit time, so an invalid schema is rejected on the `POST` rather than after a transcription you would have been billed for. **Billing** is identical to the synchronous endpoint and happens in the background. Pass `include_usage=true` on `GET /extract/video/{task_id}` for the breakdown. See https://docs.vidnavigator.com/guides/async-jobs **Submit-time credit gate:** the submit endpoint returns `402` instead of a `task_id` when the account has less than 60 seconds of transcription credit left. This is a spam gate, not a quote — the real, duration-accurate charge happens when the job runs, and a job whose video exceeds the remaining balance still fails on the same credit check a synchronous call would hit. An invalid or deactivated API key is rejected with `401`, and a key without the endpoint's permission with `403`; no task is created in either case. Extract structured data from an online video of **any length**. The job runs in the background: you get a `task_id` immediately, then collect the result by polling or through a [webhook](/webhooks). Use this endpoint instead of [`POST /extract/video`](/api-reference/endpoint/extract-video) when speech-to-text is needed (`transcribe=true`) and the video may run past **10 minutes** — the synchronous endpoint rejects it with `video_too_long`. The extraction itself is fast; the transcription in front of it is what takes time. ## How It Works `POST /extract/video/async` with the **same body** as [`POST /extract/video`](/api-reference/endpoint/extract-video), plus an optional `webhook_url`. You get `202 Accepted` with a `task_id` and a `check_status_url`. Poll `GET /extract/video/{task_id}` every few seconds while `task_status` is `processing` — polling is free — or receive a [webhook](/webhooks) when the job finishes. On `completed`, `data.result` is identical to the synchronous response's `data` block, so the same parsing code works for both. On `failed`, `data.error` carries the same error code the synchronous endpoint would have returned. Accepts the same body as the synchronous endpoint — JSON, YAML and `multipart/form-data` all work. The schema is validated at submit time, so an invalid schema is rejected on the `POST`, before any transcription is billed. ## Billing Billed **exactly like** [`POST /extract/video`](/api-reference/endpoint/extract-video) — the async mode costs nothing extra. Charges are made in the background worker, so pass `include_usage=true` on the result request (not on the submit) to see them. A failed job has all of its charges reverted. ## Submit the job `POST https://api.vidnavigator.com/v1/extract/video/async` | Parameter | Type | Required | Description | | - | - | - | - | | `video_url` | string | Yes | URL of the video to extract data from. | | `schema` | object | Yes | Extraction schema (same rules as the synchronous endpoint). | | `what_to_extract` | string | No | Optional guidance for what to extract. | | `transcribe` | boolean | No | Auto-transcribe when no platform transcript is available. Default `true`. | | `webhook_url` | string | No | Where to POST the result when the job finishes. Overrides the account default set in [Studio → API](https://vidnavigator.com/studio/api); pass `""` to opt this job out of the default. Must be a public `https` URL. See [Webhooks](/webhooks). | `include_usage` is not accepted on the submit request — pass it on the poll request instead. ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/extract/video/async" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.tiktok.com/@user/video/1234567890", "schema": { "main_topics": { "type": "Array", "description": "List of main topics discussed", "items": { "type": "String", "description": "A topic" } }, "key_takeaway": { "type": "String", "description": "The single most important takeaway" } }, "webhook_url": "https://example.com/hooks/vidnavigator" }' ``` ```python Python theme={null} import time import requests BASE = "https://api.vidnavigator.com" HEADERS = {"X-API-Key": "YOUR_API_KEY"} task = requests.post( f"{BASE}/v1/extract/video/async", headers=HEADERS, json={ "video_url": "https://www.tiktok.com/@user/video/1234567890", "schema": { "key_takeaway": {"type": "String", "description": "The single most important takeaway"} }, }, ).json()["data"] while True: job = requests.get(BASE + task["check_status_url"], headers=HEADERS).json()["data"] if job["task_status"] != "processing": break time.sleep(5) if job["task_status"] == "completed": print(job["result"]) # the extracted fields print(job["video_info"]["title"]) # the video's metadata else: print("Failed:", job["error"]["error"], job["error"]["message"]) ``` ```javascript JavaScript theme={null} const BASE = 'https://api.vidnavigator.com'; const HEADERS = { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; const { data: task } = await (await fetch(`${BASE}/v1/extract/video/async`, { method: 'POST', headers: HEADERS, body: JSON.stringify({ video_url: 'https://www.tiktok.com/@user/video/1234567890', schema: { key_takeaway: { type: 'String', description: 'The single most important takeaway' } } }) })).json(); let job; do { await new Promise(r => setTimeout(r, 5000)); ({ data: job } = await (await fetch(BASE + task.check_status_url, { headers: HEADERS })).json()); } while (job.task_status === 'processing'); console.log(job.task_status === 'completed' ? { data: job.result, video_info: job.video_info } : job.error); ``` The [Python](/sdks/python#structured-extraction) and [JavaScript](/sdks/javascript#structured-extraction) SDKs (v2.1.0) do the submit and polling for you: `client.extract_video_data(...)` / `vn.extractVideoData(...)` return the extracted `data` and the video's `video_info` in one call. ### Response (202 Accepted) ```json theme={null} { "status": "success", "data": { "task_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "task_status": "processing", "job_type": "extract_video", "expires_at": "2026-09-23T13:00:00Z", "check_status_url": "/v1/extract/video/7c9e6679-7425-40de-944b-e07fc1f90ae7", "webhook_url": "https://example.com/hooks/vidnavigator", "message": "Extraction job accepted." } } ``` | Status | `error` | Description | | - | - | - | | `400` | `missing_parameter`, `request_body_required`, `invalid_schema`, `invalid_parameter` | Invalid request or schema. | | `401` | — | Invalid or deactivated API key. | | `402` | `limit_exceeded` | Less than 60 seconds of transcription credit left. No task is created. | | `403` | — | The API key lacks permission for this endpoint. | | `429` | `too_many_active_jobs` | Too many jobs already running for this account. | ## Get the result The playground above only covers the submit request. Call the result endpoint with cURL or your HTTP client. `GET https://api.vidnavigator.com/v1/extract/video/{task_id}` | Parameter | In | Required | Description | | - | - | - | - | | `task_id` | path | Yes | The `task_id` returned by `POST /extract/video/async`. | | `include_usage` | query | No | When `true` and `task_status=completed`, attaches a `usage` block with the final charges. | Polling is **free**. Results are kept for **1 hour after the job finishes**, and reading a task doesn't delete it. On `completed`, `data.result` is **identical** to the `data` block of a successful [synchronous `POST /extract/video`](/api-reference/endpoint/extract-video) response, and `data.video_info` holds the same `video_info` the synchronous endpoint returns beside it. ```json theme={null} { "status": "success", "data": { "task_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "task_status": "completed", "job_type": "extract_video", "created_at": "2026-09-23T12:00:00Z", "started_at": "2026-09-23T12:00:01Z", "completed_at": "2026-09-23T12:04:10Z", "expires_at": "2026-09-23T13:04:10Z", "check_status_url": "/v1/extract/video/7c9e6679-7425-40de-944b-e07fc1f90ae7", "webhook": null, "result": { "main_topics": ["machine learning", "neural networks"], "key_takeaway": "Start with clean data before choosing a model architecture" }, "video_info": { "title": "Machine Learning Fundamentals", "channel": "Tech Academy", "duration": 1843.0, "url": "https://www.youtube.com/watch?v=abc123" }, "error": null } } ``` `video_info` is only present once `task_status` is `completed`. On `failed`, `data.result` is `null` and `data.error` carries the same code the synchronous endpoint would have returned (e.g. `transcript_not_available`, `http_status: 404`). All charges are reverted. | Status | `error` | Description | | - | - | - | | `404` | `task_not_found` | Unknown `task_id`, expired, owned by another account, or not an extraction task. | ## Webhook Pass `webhook_url` on the submit request, or configure a default endpoint in [Studio → API](https://vidnavigator.com/studio/api), to be called back when the job finishes. See [Webhooks](/webhooks) for the payload and signature verification, and [Async Jobs](/guides/async-jobs) for the full workflow. # Get File Info Source: https://docs.vidnavigator.com/api-reference/endpoint/file-info GET /file/{file_id} Retrieve detailed information about a specific file including its transcript if available Retrieve detailed information about a specific uploaded file, including its transcript once processing is complete. ## Path Parameter * `file_id` (string, required) – The ID of the uploaded file. ## Example Request ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/file/file_abc123" \ -H "X-API-Key: YOUR_API_KEY" ``` ## Success Response (200 OK) Returns a `file_info` object (including namespace assignments) and, if processing is complete, a `transcript` object. ```json theme={null} { "status": "success", "data": { "file_info": { "id": "file_abc123", "name": "session.mp4", "size": 15728640, "type": "video/mp4", "duration": 330.5, "status": "completed", "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z", "has_transcript": true, "namespace_ids": ["64a1b2c3d4e5f6789abc0001"], "namespaces": [ { "id": "64a1b2c3d4e5f6789abc0001", "name": "default" } ] }, "transcript": [ { "text": "Welcome to this tutorial...", "start": 0.0, "end": 3.5 } ] } } ``` # Get Temporary File URL Source: https://docs.vidnavigator.com/api-reference/endpoint/file-url GET /file/{file_id}/url Returns a short-lived (~1 hour) signed storage URL pointing directly at the file bytes. Same model as AWS S3 / CloudFront pre-signed URLs: the URL itself is the capability, time-limited and resource-scoped, so it can be used from browsers, no-code tools, or any HTTP client without forwarding header auth. Call this endpoint again at any time to mint a fresh URL. Generate a temporary, signed URL to directly access and read an uploaded file. This URL is valid for 1 hour. ## Path Parameter * `file_id` (string, required) ## Example Request ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/file/file_abc123/url" \ -H "X-API-Key: YOUR_API_KEY" ``` ## Success Response (200 OK) Returns a temporary, signed URL that can be used to access the file directly. The URL will expire after a short period. ```json theme={null} { "status": "success", "data": { "file_id": "file_abc123", "file_url": "https://signed.aws.url/file_abc123?exp=1700000000" } } ``` # API Health Check Source: https://docs.vidnavigator.com/api-reference/endpoint/health GET /health Check API health and get endpoint information Check API health status and get a list of all available endpoints. This endpoint does **not** require authentication. ## Example Request ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/health" ``` ## Success Response ```json theme={null} { "status": "success", "message": "API is healthy", "version": "1.0.0", "endpoints": [ { "path": "/transcript", "method": "POST", "description": "Get transcript for any supported video (incl. YouTube)", "auth_required": true }, { "path": "/transcribe", "method": "POST", "description": "Transcribe online videos", "auth_required": true }, { "path": "/analyze/video", "method": "POST", "description": "Analyze online video", "auth_required": true }, { "path": "/analyze/file", "method": "POST", "description": "Analyze uploaded file", "auth_required": true }, { "path": "/extract/video", "method": "POST", "description": "Extract structured data from online video", "auth_required": true }, { "path": "/extract/file", "method": "POST", "description": "Extract structured data from uploaded file", "auth_required": true }, { "path": "/youtube/search", "method": "POST", "description": "Search YouTube for videos", "auth_required": true }, { "path": "/tiktok/profile", "method": "POST", "description": "Submit a TikTok profile scrape", "auth_required": true }, { "path": "/tiktok/profile/{task_id}", "method": "GET", "description": "Get TikTok profile scrape result", "auth_required": true }, { "path": "/tiktok/search", "method": "POST", "description": "Submit a TikTok keyword search", "auth_required": true }, { "path": "/tiktok/search/{task_id}", "method": "GET", "description": "Get TikTok keyword search result", "auth_required": true }, { "path": "/search/file", "method": "POST", "description": "Search uploaded files", "auth_required": true }, { "path": "/files", "method": "GET", "description": "List uploaded files", "auth_required": true }, { "path": "/file/{file_id}", "method": "GET", "description": "Get file info and transcript", "auth_required": true }, { "path": "/file/{file_id}/url", "method": "GET", "description": "Get temporary file URL", "auth_required": true }, { "path": "/upload/file", "method": "POST", "description": "Upload a file", "auth_required": true }, { "path": "/file/{file_id}/retry", "method": "POST", "description": "Retry failed file processing", "auth_required": true }, { "path": "/file/{file_id}/cancel", "method": "POST", "description": "Cancel file processing", "auth_required": true }, { "path": "/file/{file_id}/delete", "method": "DELETE", "description": "Delete a file", "auth_required": true }, { "path": "/namespaces", "method": "GET", "description": "List namespaces", "auth_required": true }, { "path": "/namespaces", "method": "POST", "description": "Create a namespace", "auth_required": true }, { "path": "/namespaces/{namespace_id}", "method": "PUT", "description": "Rename a namespace", "auth_required": true }, { "path": "/namespaces/{namespace_id}", "method": "DELETE", "description": "Delete a namespace", "auth_required": true }, { "path": "/file/{file_id}/namespaces", "method": "PUT", "description": "Update file namespace assignments", "auth_required": true }, { "path": "/usage", "method": "GET", "description": "Get usage statistics", "auth_required": true }, { "path": "/health", "method": "GET", "description": "Health check", "auth_required": false } ] } ``` # List Files Source: https://docs.vidnavigator.com/api-reference/endpoint/list-files GET /files Retrieve a paginated list of all files uploaded by the authenticated user. Supports filtering by status and namespace. Retrieve a paginated list of all audio/video files you have uploaded. Supports filtering by status and namespace. ## Overview Use this endpoint to get basic information on every file you have uploaded, including status, metadata, and namespace assignments. Supports pagination with `limit` and `offset` query parameters. ## Query Parameters * `limit` (integer, default `50`): Maximum number of files to return (1-100) * `offset` (integer, default `0`): Number of files to skip for pagination * `status` (string): Filter by processing status (`processing`, `completed`, `failed`, `cancelled`) * `namespace_id` (string): Filter by namespace ID. Only returns files belonging to this namespace. If not set, returns files from all namespaces. ## Example Request ```bash cURL theme={null} curl -G "https://api.vidnavigator.com/v1/files" \ -H "X-API-Key: YOUR_API_KEY" \ --data-urlencode "limit=25" \ --data-urlencode "offset=0" \ --data-urlencode "status=completed" ``` ```bash cURL (filter by namespace) theme={null} curl -G "https://api.vidnavigator.com/v1/files" \ -H "X-API-Key: YOUR_API_KEY" \ --data-urlencode "namespace_id=64a1b2c3d4e5f6789abc0002" ``` ## Success Response ```json theme={null} { "status": "success", "data": { "files": [ { "id": "file_abc123", "name": "session.mp4", "size": 15728640, "type": "video/mp4", "duration": 330, "status": "completed", "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z", "has_transcript": true, "namespace_ids": ["64a1b2c3d4e5f6789abc0001", "64a1b2c3d4e5f6789abc0002"], "namespaces": [ { "id": "64a1b2c3d4e5f6789abc0001", "name": "default" }, { "id": "64a1b2c3d4e5f6789abc0002", "name": "Client Calls" } ] } ], "total_count": 1, "limit": 25, "offset": 0, "has_more": false } } ``` # List Namespaces Source: https://docs.vidnavigator.com/api-reference/endpoint/list-namespaces GET /namespaces Get all namespaces for the authenticated user. A 'default' namespace is auto-created if none exist. Files without a namespace are lazily migrated to the default. Retrieve all namespaces for your account. Namespaces let you organize uploaded files into logical groups (like folders). ## Overview Namespaces help you organize your uploaded files into categories such as "Client Calls", "Product Demos", or "Training Videos". A `default` namespace is automatically created if you don't have any yet, and files without a namespace are lazily migrated to it. ## Example Request ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/namespaces" \ -H "X-API-Key: YOUR_API_KEY" ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/namespaces" headers = {"X-API-Key": "YOUR_API_KEY"} response = requests.get(url, headers=headers) namespaces = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/namespaces', { headers: { 'X-API-Key': 'YOUR_API_KEY' } }); const namespaces = await response.json(); ``` ## Success Response ```json theme={null} { "status": "success", "data": [ { "id": "64a1b2c3d4e5f6789abc0001", "name": "default", "created_at": "2025-01-10T08:00:00Z", "updated_at": "2025-01-10T08:00:00Z" }, { "id": "64a1b2c3d4e5f6789abc0002", "name": "Client Calls", "created_at": "2025-02-15T14:30:00Z", "updated_at": "2025-02-15T14:30:00Z" } ] } ``` # Rename Namespace Source: https://docs.vidnavigator.com/api-reference/endpoint/rename-namespace PUT /namespaces/{namespace_id} Rename an existing namespace. The 'default' namespace cannot be renamed. Rename an existing namespace. The `default` namespace cannot be renamed. ## Example Request ```bash cURL theme={null} curl -X PUT "https://api.vidnavigator.com/v1/namespaces/64a1b2c3d4e5f6789abc0003" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Product Demos" }' ``` ```python Python theme={null} import requests namespace_id = "64a1b2c3d4e5f6789abc0003" url = f"https://api.vidnavigator.com/v1/namespaces/{namespace_id}" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = {"name": "Product Demos"} response = requests.put(url, headers=headers, json=data) result = response.json() ``` ```javascript JavaScript theme={null} const namespaceId = '64a1b2c3d4e5f6789abc0003'; const response = await fetch(`https://api.vidnavigator.com/v1/namespaces/${namespaceId}`, { method: 'PUT', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Product Demos' }) }); const result = await response.json(); ``` ## Success Response ```json theme={null} { "status": "success", "message": "Namespace renamed successfully" } ``` # Retry File Processing Source: https://docs.vidnavigator.com/api-reference/endpoint/retry-file POST /file/{file_id}/retry Retry processing for a file that previously failed or was cancelled Restart processing for a file that previously failed or was cancelled. ## Path Parameter * `file_id` (string, required) ## Example Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/file/file_abc123/retry" \ -H "X-API-Key: YOUR_API_KEY" ``` ## Success Response (200 OK) Returns a confirmation that the file processing has been restarted. The `file_status` will be updated to `processing`. ```json theme={null} { "status": "success", "data": { "file_id": "file_abc123", "file_name": "failed_video.mp4", "file_status": "processing", "message": "File processing restarted successfully" } } ``` # Search Files Source: https://docs.vidnavigator.com/api-reference/endpoint/search-file POST /search/file Search through user's uploaded files using vector similarity with AI reranking. **Namespace filtering:** Pass `namespace_ids` to restrict the search to files in specific namespaces. If omitted, all namespaces are searched. **Process:** 1. Vector similarity search using text embeddings 2. Initial ranking by semantic similarity 3. AI reranking for improved relevance 4. Rich metadata and signed URLs for file access 5. Optional per-file AI analysis for people, places, key subjects, and key moments Each result includes the `namespace_ids` and resolved `namespaces` the file belongs to. **Billing:** one `search_request` per call. The AI analysis pass (per-file + overall aggregation) bills `analysis_request` as `ceil(total_tokens / 15000)` units (minimum 1 when analysis ran). Set `include_usage=true` to receive the per-charge breakdown — the consolidated `analysis_request` entry carries a nested `tokens` object. Search your uploaded files using vector similarity and AI reranking. ## Overview This endpoint searches through your uploaded file library using OpenAI embeddings for semantic similarity, followed by AI reranking for improved relevance. Results include rich metadata, signed URLs, and optional per-file analysis. ## Billing File search is a semantic (vector) search over already indexed content and consumes **one `search_request`** per call. 1 credit covers 1,000 semantic searches. If the AI analysis pass runs (per-file + overall aggregation), it also bills `analysis_request` as `ceil(total_tokens / 15000)` units (minimum 1 when analysis ran). Set `include_usage: true` to receive a [`usage`](/api-reference/usage-costs#per-call-usage-disclosure) block with the per-charge breakdown — the consolidated `analysis_request` entry carries a nested `tokens` object. ### Search Process 1. Vector similarity search using OpenAI embeddings 2. Initial ranking by semantic similarity 3. AI reranking for improved relevance 4. Rich metadata and signed URLs for file access 5. Optional per-file AI analysis for people, places, key subjects, and key moments ## Request Body | Parameter | Type | Required | Description | | - | - | - | - | | `query` | string | Yes | Search query | | `namespace_ids` | string\[] | No | Restrict search to specific namespaces. If omitted, searches all files. | ## Example Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/search/file" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What did the customer say about pricing?" }' ``` ```bash cURL (with namespace filter) theme={null} curl -X POST "https://api.vidnavigator.com/v1/search/file" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What did the customer say about pricing?", "namespace_ids": ["64a1b2c3d4e5f6789abc0002"] }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/search/file" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "query": "What did the customer say about pricing?", "namespace_ids": ["64a1b2c3d4e5f6789abc0002"] } response = requests.post(url, headers=headers, json=data) results = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/search/file', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'What did the customer say about pricing?', namespace_ids: ['64a1b2c3d4e5f6789abc0002'] }) }); const results = await response.json(); ``` ## Success Response ```json theme={null} { "status": "success", "data": { "results": [ { "id": "file_abc123", "name": "client-call-march.mp4", "duration": 1800, "size": 52428800, "type": "video/mp4", "status": "completed", "created_at": "2026-03-10T14:00:00Z", "file_url": "https://storage.example.com/signed-url...", "namespace_ids": ["64a1b2c3d4e5f6789abc0002"], "namespaces": [ { "id": "64a1b2c3d4e5f6789abc0002", "name": "Client Calls" } ], "relevance_score": 0.92, "timestamps": [245.5, 890.2], "relevant_text": "The customer mentioned the pricing was competitive but wished for a monthly option.", "query_answer": "The customer found pricing competitive but requested monthly billing.", "transcript_summary": "A 30-minute client call discussing product features, pricing, and onboarding timeline.", "people": [ { "name": "John Smith", "context": "Client representative" } ], "key_subjects": [ { "name": "Pricing", "description": "Discussion about pricing tiers and billing options" } ] } ], "query": "What did the customer say about pricing?", "total_found": 1, "explanation": "Found 1 file matching your query about customer pricing feedback." } } ``` # TikTok Profile Scrape Source: https://docs.vidnavigator.com/api-reference/endpoint/tiktok-profile POST /tiktok/profile Start an asynchronous TikTok profile scrape and return a `task_id` immediately. The scrape runs in the background with shallow extraction so it can stop early when filters are satisfied. Results are retained for 1 hour and, when cloud storage is configured, also uploaded as a single JSON file accessible through `download_url`. **Billing:** Each TikTok API page fetched is billed as one standard request. One page is pre-charged when the task is accepted; remaining pages are reconciled when the scrape completes. If the scrape fails after charging additional pages, all charged pages are reversed. TikTok currently returns ~15 videos per page; this is an internal estimate and may change. **Filters:** All filters are applied as the scraper iterates. `max_posts` and `after_datetime` allow the scraper to stop fetching new pages early; the other filters reduce result size but do not stop pagination. **Usage disclosure:** This endpoint does NOT accept `include_usage`. Because billing happens in the background worker after the 202 response is sent, there is nothing to disclose at submit time. Pass `include_usage=true` on `GET /tiktok/profile/{task_id}` instead — the GET endpoint replays the final charges once `task_status=completed`. Use `GET /tiktok/profile/{task_id}` to poll status and retrieve paginated results, or follow `download_url` for the complete JSON. Start an asynchronous scrape of a public TikTok profile and return a `task_id` immediately. ## Overview This endpoint is designed for profile-level collection, not single-video analysis. It starts a background scrape job that iterates through a TikTok account, applies your filters as it goes, and stores the results temporarily so you can fetch them later. Use it when you want to: * collect recent posts from a TikTok creator * filter videos by date or like count * scrape large profiles without blocking on a long request * export the full result set through a temporary `download_url` ## How the Async Flow Works 1. Send `POST /tiktok/profile` with a public TikTok `profile_url` and optional filters. 2. The API accepts the task and returns a `task_id` with `task_status: "processing"`. 3. Poll [`GET /tiktok/profile/{task_id}`](#get-the-result) until the task is `completed` or `failed`. 4. Once completed, either page through `videos` using cursors or download the full JSON from `download_url` when available. Results are temporary. Tasks expire about 1 hour after creation, including `download_url` availability. ## Filters You Can Apply All filters are optional except `profile_url`. | Field | Type | Description | | - | - | - | | `profile_url` | string | Public TikTok profile URL, such as `https://www.tiktok.com/@username` | | `max_posts` | integer | Maximum number of matching videos to return | | `after_datetime` | string | Include only videos published on or after this boundary. Accepts `YYYY-MM-DD` or an ISO datetime with timezone, such as `2024-01-01T00:00:00Z`. | | `before_datetime` | string | Include only videos published on or before this boundary. Accepts `YYYY-MM-DD` or an ISO datetime with timezone, such as `2024-12-31T23:59:59+02:00`. | | `min_likes` | integer | Include only videos with at least this many likes | | `max_likes` | integer | Include only videos with at most this many likes | | `webhook_url` | string | Where to POST a notification when the task finishes. Overrides the account default set in [Studio → API](https://vidnavigator.com/studio/api); pass `""` to opt this task out of the default. Must be a public `https` URL. See [Webhooks](/webhooks). | The TikTok webhook event is a **notification, not the payload**: a scrape can hold thousands of videos, so the event carries `stats` and tells you to read the results from `check_status_url`. `max_posts` and `after_datetime` are the most useful filters for keeping scrapes smaller and faster because they can stop pagination early. ## What You Get Back Immediately The submit endpoint now returns more than just a task ID. The accepted response includes: * `task_id` to identify the scrape job * `task_status` set to `processing` * `profile_url` echoing the submitted profile * `expires_at` so you know when the task record will expire * `check_status_url` with the relative API path to poll * `webhook_url`: the URL the completion notification will be POSTed to, or `null` * `message` confirming the job was accepted ## Example Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tiktok/profile" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_url": "https://www.tiktok.com/@tiktok", "max_posts": 50, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 10000 }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.vidnavigator.com/v1/tiktok/profile", headers={ "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }, json={ "profile_url": "https://www.tiktok.com/@tiktok", "max_posts": 50, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 10000, }, ) print(response.json()) ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/tiktok/profile', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ profile_url: 'https://www.tiktok.com/@tiktok', max_posts: 50, after_datetime: '2024-01-01T00:00:00Z', min_likes: 10000 }) }); const result = await response.json(); ``` ## Success Response The endpoint returns `202 Accepted` because the scrape runs in the background. ```json theme={null} { "status": "success", "data": { "task_id": "tt_profile_abc123", "task_status": "processing", "profile_url": "https://www.tiktok.com/@tiktok", "expires_at": "2026-04-26T00:55:00Z", "check_status_url": "/v1/tiktok/profile/tt_profile_abc123", "webhook_url": null, "message": "TikTok profile scrape accepted. Poll the status endpoint to retrieve results." } } ``` ## Billing Notes TikTok profile scraping is billed by pages consumed during the scrape: * each TikTok API page is billed as one `standard_request` when no proxy is used * each TikTok API page is billed as one `residential_request` when a residential proxy is used * one page is pre-charged when the task is accepted * final usage is reconciled when the scrape completes * if the scrape fails after extra pages were charged, those charges are reversed TikTok currently returns about 15 videos per page, but that is an implementation detail and may change. ## Tips for Large Profiles * use `after_datetime` to avoid crawling older history you do not need * use `max_posts` to cap the result size * prefer `download_url` after completion if you expect a large result set * poll the result endpoint while the task is processing instead of resubmitting the same scrape ## Common Errors * `request_body_required`: the JSON body was missing * `missing_parameter`: `profile_url` was not sent * `invalid_url`: the submitted `profile_url` is malformed * `invalid_parameter`: one of the filters is malformed The OpenAPI spec no longer lists `unsupported_platform` for this endpoint. ## Get the result `GET /tiktok/profile/{task_id}` — poll this endpoint with the `task_id` (or the `check_status_url`) returned above, or wait for the [webhook](/webhooks) notification. Polling does **not** consume credits. The playground above only covers the submit request. Call the result endpoint with cURL or your HTTP client. ### Request Parameters | Parameter | In | Required | Description | | - | - | - | - | | `task_id` | path | Yes | Task ID returned by `POST /tiktok/profile` | | `cursor` | query | No | Opaque pagination cursor from a previous response | | `limit` | query | No | Maximum number of videos to return, between `1` and `500`. Default is `50`. | ### How Pagination Works Pagination is cursor-based, but the cursor encodes an absolute offset. That means you can safely change `limit` between requests without skipping or duplicating videos. Typical flow: 1. Call the endpoint with no `cursor`. 2. Read `data.pagination.next_cursor`. 3. Pass that value back as the next request's `cursor`. 4. Stop when `has_next` is `false`. ### Polling Lifecycle The `task_status` field will be one of: * `processing`: the scrape is still running * `completed`: the scrape finished and results are ready * `failed`: the scrape ended with an error While the task is still processing: * `profile` may be `null` * `videos` will be empty * `download_url` will usually be `null` Once completed: * `videos` contains a cursor-paginated page of matching posts * `pagination` tells you whether more pages exist * `download_url` may contain a temporary signed URL for the full JSON result ### Example Requests ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/tiktok/profile/tt_profile_abc123?limit=25" \ -H "X-API-Key: YOUR_API_KEY" ``` ```python Python theme={null} import requests response = requests.get( "https://api.vidnavigator.com/v1/tiktok/profile/tt_profile_abc123", headers={"X-API-Key": "YOUR_API_KEY"}, params={"limit": 25}, ) print(response.json()) ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://api.vidnavigator.com/v1/tiktok/profile/tt_profile_abc123?limit=25', { headers: { 'X-API-Key': 'YOUR_API_KEY' } } ); const result = await response.json(); ``` #### Fetch the Next Page ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/tiktok/profile/tt_profile_abc123?limit=25&cursor=eyJvZmZzZXQiOjI1fQ==" \ -H "X-API-Key: YOUR_API_KEY" ``` ### Example Completed Response ```json theme={null} { "status": "success", "data": { "task_id": "tt_profile_abc123", "task_status": "completed", "profile_url": "https://www.tiktok.com/@tiktok", "profile": { "uploader": "tiktok", "follower_count": 12345678 }, "filters": { "max_posts": 50, "after_datetime": "2024-01-01T00:00:00Z", "before_datetime": null, "min_likes": 10000, "max_likes": null }, "stats": { "videos_scanned": 120, "videos_matched": 50, "pages_consumed": 8 }, "videos": [ { "id": "7351234567890123456", "track": "Original sound", "artists": ["Creator Name"], "duration": 28, "title": "Example TikTok", "description": "Video caption text here", "timestamp": 1712345678, "published_at": "2024-04-05T19:34:38Z", "views": 250000, "likes": 22000, "reposts": 180, "comments": 950, "thumbnails": [], "url": "https://www.tiktok.com/@tiktok/video/7351234567890123456" } ], "pagination": { "limit": 25, "offset": 0, "total_items": 50, "has_next": true, "has_prev": false, "next_cursor": "eyJvZmZzZXQiOjI1fQ==", "prev_cursor": null }, "download_url": "https://storage.googleapis.com/bucket/api/tiktok_profiles/user/task.json?...", "error_message": null, "error": null, "webhook": null, "created_at": "2026-04-25T20:00:00Z", "completed_at": "2026-04-25T20:01:12Z", "expires_at": "2026-04-25T21:00:00Z" } } ``` ### Understanding the Response #### `profile` Contains profile-level metadata returned by the scraper, such as uploader information and follower counts when available. #### `stats` Helps you understand what happened during the scrape: * `videos_scanned`: total TikTok entries iterated before filtering * `videos_matched`: entries that passed your filters * `pages_consumed`: estimated number of TikTok API pages fetched during the scrape #### `videos` This is the current page of matched TikTok videos. Each item includes public metadata such as: * `id` * `title` * `description` * `timestamp`: Unix timestamp in seconds when the video was uploaded * `published_at`: UTC datetime string derived from `timestamp`, in ISO 8601 format with timezone * `views` * `likes` * `comments` * `url` #### `download_url` When present, this is a short-lived signed URL pointing to the full scrape result as one JSON file. It is useful for large profiles, browser downloads, and no-code integrations. If it is `null`, either: * the task has not finished yet, or * the full result wasn't archived, and you should paginate through `videos` instead #### `error` Present when `task_status` is `failed`: an object with `error` (a code you can branch on), `message` and `http_status`. See [Errors](/api-reference/errors). `error_message` is **deprecated**. It is kept for existing integrations, but new code should read `error.error` / `error.message` instead. #### `webhook` Delivery state of the task's [webhook](/webhooks) notification (`status`, `attempts`, `response_status`, `last_error`, `delivered_at`), or `null` when no webhook applies. ### Error Cases * `400 invalid_cursor`: the supplied `cursor` is invalid * `404 task_not_found`: task does not exist, expired, or does not belong to the current user ### Tips * poll every few seconds while `task_status` is `processing` * switch to `download_url` for large completed jobs * do not assume tasks live forever; they expire after about 1 hour * call the endpoint again later if you need a freshly minted `download_url` * use the `expires_at` value to avoid polling a task after it has already expired # TikTok Keyword Search Source: https://docs.vidnavigator.com/api-reference/endpoint/tiktok-search POST /tiktok/search Start an asynchronous TikTok keyword search task and return a `task_id` immediately. Poll `GET /tiktok/search/{task_id}` for cursor-paginated results. Results are retained for ~1 hour. Results are returned sorted by `published_at` descending (newest first) across pagination, full retrieval, and the signed `download_url` payload, unless `sort_by` is set, in which case that order is kept. **parallel_search_slices billing:** by default (`1`), the search runs a single paginated query chain and is naturally capped at roughly 115-140 items by TikTok. Setting `parallel_search_slices` to `2..4` runs that many concurrent chains in parallel and dedups by video id, lifting the effective ceiling. Cost scales linearly: each additional slice consumes up to ~1x the residential pages of a single search, so an N-slice request bills up to **Nx the residential pages**. Extra slices add the most on searches without a date window; with `published_within` set, every slice searches the same window and can return largely the same videos. `parallel_search_slices` is *not* a time filter — use `published_within`, `after_datetime` or `before_datetime` for time windows. **Usage disclosure:** This endpoint does NOT accept `include_usage`. Billing happens in the background worker after the 202 response is sent, so there is nothing to disclose at submit time. Pass `include_usage=true` on `GET /tiktok/search/{task_id}` instead — the GET endpoint replays the final charges once `task_status=completed`. Start an asynchronous TikTok keyword search and get a `task_id` back immediately. ## Overview This endpoint runs an actual keyword search across TikTok (not a single profile) and returns multiple matching videos. It is asynchronous: the search runs in a background worker, and you poll [`GET /tiktok/search/{task_id}`](#get-the-result) for cursor-paginated results. Results are retained for \~1 hour. Results are returned sorted by `published_at` descending (newest first) across pagination, full retrieval, and the signed `download_url` payload — unless you set `sort_by`, in which case that order is kept. This is different from the [TikTok Profile scrape](/api-reference/endpoint/tiktok-profile), which walks a single account's videos. Use this endpoint to discover videos across TikTok by keyword. ## Billing Charges happen in the background worker and are disclosed on the polling endpoint (see below), not here. * Billing is `pages_fetched × residential_request`. **No `search_request`** is billed for TikTok keyword search. * **`parallel_search_slices`** scales the cost linearly. By default (`1`) the search runs a single paginated query chain, naturally capped at roughly 115–140 items by TikTok. Setting it to `2..4` runs that many concurrent chains and dedups by video id, lifting the ceiling — but an N-slice request bills up to **N× the residential pages** of a single search. Extra slices help most on searches **without** a date window: with `published_within` set, every slice searches the same window and can return largely the same videos. **Usage disclosure:** this endpoint does **not** accept `include_usage`. Because billing happens after the `202` response is sent, there is nothing to disclose at submit time. Pass `include_usage=true` on [`GET /tiktok/search/{task_id}`](#get-the-result) instead — it replays the final charges once `task_status=completed`. ## Request Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `query` | string | Yes | Keyword phrase to search on TikTok (min length 2). | | `max_results` | integer | No | Upper bound on results the task will try to collect. `0` (default) or omitted means **unlimited** — pagination stops only when TikTok has no more results or the page cap is reached (\~115–140 items per slice: \~5 pages of \~30 items). No hard server-side cap is enforced; you are billed per residential page actually fetched. | | `parallel_search_slices` | integer | No | How many concurrent paginated query chains to run and dedup (`1`–`4`, default `1`). Higher values lift the result ceiling at up to **\~N× the residential pages** of a single search. With `published_within` set, keep `1` unless you have measured a gain. Not a time filter — use `published_within`, `after_datetime` or `before_datetime` for time windows. | | `sort_by` | string | No | Sort order applied by TikTok itself (same as the app's search Filters): `relevance`, `most_liked` or `newest`. See [Sorting and date windows](#sorting-and-date-windows). | | `published_within` | string | No | Publication window applied by TikTok itself: `all`, `past_24_hours`, `this_week`, `this_month`, `last_3_months` or `last_6_months`. Windows are rolling (`this_week` = the last 7 days). | | `after_datetime` | string | No | Only include videos published on or after this boundary. Accepts `YYYY-MM-DD` or an ISO datetime with timezone. Unless `sort_by` or `published_within` is set, it also makes the search run newest first and stop once it passes this boundary. | | `before_datetime` | string | No | Only include videos published on or before this boundary. Accepts `YYYY-MM-DD` or an ISO datetime with timezone. | | `min_likes` | integer | No | Only include videos with at least this many likes. | | `max_likes` | integer | No | Only include videos with at most this many likes. | | `min_views` | integer | No | Only include videos with at least this many views. | | `max_views` | integer | No | Only include videos with at most this many views. | | `webhook_url` | string | No | Where to POST a notification when the task finishes. Overrides the account default set in [Studio → API](https://vidnavigator.com/studio/api); pass `""` to opt this task out of the default. Must be a public `https` URL. The event carries `stats` and points you to `check_status_url` for the results. See [Webhooks](/webhooks). | **Diminishing returns on slices:** on a representative query without a date window, 2 slices yield \~+43% unique items, 3 slices \~+22%, and 4 slices \~+13% — so 4 already saturates TikTok's natural \~480-item dedup ceiling for most queries. ## Sorting and date windows `sort_by` and `published_within` are applied **by TikTok itself**, before VidNavigator's own filters (`after_datetime`, `min_likes`, …). Using them well returns more matching videos per billed page. | `sort_by` | Order | | - | - | | `relevance` | TikTok's own ranking | | `most_liked` | By like count | | `newest` | By publish date, newest first | **When `sort_by` is omitted**, results are returned newest first, and the search is ordered to fit your filters: * `after_datetime` or `before_datetime` set → the search runs **newest first** and stops once it passes `after_datetime` * only `min_likes` or `min_views` set → the search runs **most liked first** **When `published_within` is omitted** and `after_datetime` is more than 24 hours ago, the smallest window that covers `after_datetime` is selected automatically. The window actually used is reported in `stats.published_within` on the [result endpoint](#get-the-result). ```json theme={null} { "query": "ai tools", "sort_by": "most_liked", "published_within": "this_week", "min_views": 10000 } ``` ## Example Usage ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tiktok/search" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "ai tools", "parallel_search_slices": 2, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 1000 }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/tiktok/search" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "query": "ai tools", "parallel_search_slices": 2, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 1000 } response = requests.post(url, headers=headers, json=data) task = response.json() print("Task ID:", task["data"]["task_id"]) ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/tiktok/search', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'ai tools', parallel_search_slices: 2, after_datetime: '2024-01-01T00:00:00Z', min_likes: 1000 }) }); const task = await response.json(); console.log('Task ID:', task.data.task_id); ``` ## Success Response (202 Accepted) ```json theme={null} { "status": "success", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "task_status": "processing", "query": "ai tools", "max_results": 0, "parallel_search_slices": 2, "filters": { "sort_by": null, "published_within": null, "after_datetime": "2024-01-01T00:00:00Z", "before_datetime": null, "min_likes": 1000, "max_likes": null, "min_views": null, "max_views": null }, "expires_at": "2026-04-26T13:00:00Z", "check_status_url": "/v1/tiktok/search/550e8400-e29b-41d4-a716-446655440000", "webhook_url": null, "message": "TikTok search task accepted and queued." } } ``` ## Get the result `GET /tiktok/search/{task_id}` — poll this endpoint with the `task_id` (or the `check_status_url`) returned above, or wait for the [webhook](/webhooks) notification. Polling does **not** consume credits. The playground above only covers the submit request. Call the result endpoint with cURL or your HTTP client. ### Request Parameters | Parameter | In | Required | Description | | - | - | - | - | | `task_id` | path | Yes | Task ID returned by `POST /tiktok/search` | | `cursor` | query | No | Opaque pagination cursor from a previous response | | `limit` | query | No | Maximum number of items to return, between `1` and `500`. Default is `50`. | | `include_usage` | query | No | When `true` and `task_status=completed`, attach a `usage` block describing the charges the background worker recorded (`pages_fetched × residential_request`; no `search_request` for TikTok keyword search). | ### Usage Disclosure Because billing happens in the background worker, the submit endpoint cannot disclose usage. Instead, pass `include_usage=true` here: * Returned **only** when `task_status=completed` (processing tasks haven't finished billing; failed tasks have all charges refunded). * Charges are `pages_fetched × residential_request`. No `search_request` is billed for TikTok keyword search. ### Polling Lifecycle The `task_status` field will be one of: * `processing`: the search is still running * `completed`: the search finished and results are ready * `failed`: the search ended with an error (all charges refunded) While processing, `results` will be empty and `download_url` will usually be `null`. ### Example Requests ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/tiktok/search/tt_search_abc123?limit=25&include_usage=true" \ -H "X-API-Key: YOUR_API_KEY" ``` ```python Python theme={null} import requests response = requests.get( "https://api.vidnavigator.com/v1/tiktok/search/tt_search_abc123", headers={"X-API-Key": "YOUR_API_KEY"}, params={"limit": 25, "include_usage": "true"}, ) print(response.json()) ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://api.vidnavigator.com/v1/tiktok/search/tt_search_abc123?limit=25&include_usage=true', { headers: { 'X-API-Key': 'YOUR_API_KEY' } } ); const result = await response.json(); ``` ### Example Completed Response ```json theme={null} { "status": "success", "data": { "task_id": "tt_search_abc123", "task_status": "completed", "query": "ai tools", "parallel_search_slices": 2, "filters": { "sort_by": null, "published_within": null, "after_datetime": "2024-01-01T00:00:00Z", "before_datetime": null, "min_likes": 1000, "max_likes": null, "min_views": null, "max_views": null }, "stats": { "pages_fetched": 18, "results_count": 172, "next_search_cursor": null, "sort_by": "relevance", "published_within": "all" }, "results": [ { "id": "7351234567890123456", "item_type": 1, "description": "Top AI tools you should try", "timestamp": 1712345678, "published_at": "2024-04-05T19:34:38Z", "author": { "id": "6789", "unique_id": "creator", "nickname": "Creator Name", "sec_uid": "MS4wLjAB..." }, "stats": { "views": 250000, "likes": 22000, "comments": 950, "shares": 120, "collects": 340 }, "music": { "id": "1122", "title": "Original sound", "author_name": "creator", "duration": 30 }, "duration": 28, "hashtags": ["ai", "tools", "productivity"], "url": "https://www.tiktok.com/@creator/video/7351234567890123456" } ], "pagination": { "limit": 25, "offset": 0, "total_items": 172, "has_next": true, "has_prev": false, "next_cursor": "eyJvZmZzZXQiOjI1fQ==", "prev_cursor": null }, "download_url": "https://storage.googleapis.com/bucket/api/tiktok_searches/user/task.json?...", "error_message": null, "error": null, "webhook": null, "created_at": "2026-04-26T12:00:00Z", "completed_at": "2026-04-26T12:01:40Z", "expires_at": "2026-04-26T13:00:00Z" }, "usage": { "charges": [ { "service_type": "residential_request", "quantity": 18, "credits": 0.09 } ], "total_credits": 0.09 } } ``` The `usage` block appears only when `include_usage=true` and `task_status=completed`. ### Understanding the Response #### `stats` * `pages_fetched`: number of TikTok pages fetched (this is what you are billed for, as `residential_request`) * `results_count`: total normalized items collected * `next_search_cursor`: internal upstream cursor, or `null` when exhausted * `sort_by`: sort order the search ran with (`relevance` when `sort_by` was omitted) * `published_within`: date window the search ran with — shows the window chosen from `after_datetime` when `published_within` was omitted; `all` means no window #### `results` Each item is a normalized TikTok video with `id`, `description`, `timestamp`, `published_at` (UTC ISO 8601), `author`, `stats` (views/likes/comments/shares/collects), `music`, `duration`, `hashtags`, and `url`. Items are sorted by `published_at` descending (items without a timestamp go last), unless `sort_by` was set, in which case TikTok's order is kept. #### `download_url` When present, a short-lived signed URL pointing to the full search result as one JSON file (same shape as the paginated `results`, but unsliced). `null` when the task hasn't finished or the full result wasn't archived — paginate through `results` instead. #### `error` Present when `task_status` is `failed`: an object with `error` (a code you can branch on), `message` and `http_status`. See [Errors](/api-reference/errors). `error_message` is **deprecated**. It is kept for existing integrations, but new code should read `error.error` / `error.message` instead. #### `webhook` Delivery state of the task's [webhook](/webhooks) notification (`status`, `attempts`, `response_status`, `last_error`, `delivered_at`), or `null` when no webhook applies. ### Error Cases * `400`: invalid request (e.g. bad cursor) * `404`: task does not exist, expired, or does not belong to the current user ### Tips * poll every few seconds while `task_status` is `processing` * switch to `download_url` for large completed jobs * tasks expire after about 1 hour — use `expires_at` to avoid polling expired tasks * call the endpoint again later if you need a freshly minted `download_url` # Transcribe Online Video Source: https://docs.vidnavigator.com/api-reference/endpoint/transcribe POST /transcribe Transcribe online videos (e.g., Instagram, TikTok) using speech-to-text models when a transcript is not available. **Instagram Carousel Posts:** - For posts with multiple videos, the URL can include `?img_index=N` to select a specific video - Example: `https://www.instagram.com/p/ABC123/?img_index=2` transcribes the second video - Without `img_index`, the first video is transcribed - Set `all_videos=true` to transcribe ALL videos in a carousel post **Options:** - `transcript_text=true`: Returns transcript as a single text string instead of segments - `all_videos=true`: For carousel posts, returns all videos with their transcripts **Billing:** on cache miss, one `standard_request` or `residential_request` is charged for the metadata fetch (residential for Instagram, Facebook-watch, Rumble; standard otherwise), plus `transcription_hour` proportional to the audio duration. On cache hit (same URL transcribed before for any user), only `transcription_hour` is charged — the metadata fetch is skipped. Users with the `free_on_cache_hit` sponsorship flag get the cache-hit `transcription_hour` waived (`waived: true` in the per-charge entry). Set `include_usage=true` to see the breakdown. **Long videos:** this endpoint is synchronous and holds the connection open for the whole transcription. For media longer than ~10 minutes use `POST /transcribe/async` instead, which returns a `task_id` and can call your webhook when it finishes. Media longer than 10 minutes is rejected here with `video_too_long` (HTTP 400). The metadata fetch needed to read the video's duration is billed (`standard_request` or `residential_request`) and is **not** refunded: the limit is documented, so the proxy call was spent answering a request that could not be served. No `transcription_hour` is charged — no audio is processed. Send long media to the async endpoint, which has no cap. See https://docs.vidnavigator.com/guides/async-jobs Transcribe online videos from platforms like Instagram, TikTok, and more using speech-to-text models when a direct transcript is not available. ## Overview This endpoint is designed for videos where a pre-existing transcript or caption file cannot be retrieved. It downloads the video content and processes it through an AI speech-to-text model to generate a new transcript. This is particularly useful for social media platforms where direct transcript extraction is not possible, such as Instagram Reels that don't have captions. **YouTube is not supported by this endpoint.** For YouTube videos, use the [/transcript](/api-reference/endpoint/transcript) endpoint instead, which retrieves existing transcripts/captions. ## Synchronous or async? This endpoint comes in two modes, with the same request body and the same result: | Mode | Endpoint | Media duration | How you get the result | | - | - | - | - | | Synchronous | `POST /transcribe` | **Up to 10 minutes** | In the HTTP response | | Async | [`POST /transcribe/async`](/api-reference/endpoint/transcribe-async) | **Any length** | Poll `GET /transcribe/{task_id}` or receive a [webhook](/webhooks) | **Media longer than 10 minutes is rejected by the synchronous endpoint** with `400 video_too_long`. Use the [async mode](/api-reference/endpoint/transcribe-async) for anything that may run past 10 minutes — it also works for short videos, so it is the safe default when you don't know the duration. ## Billing Speech-to-text processing consumes `transcription_hour` usage. 1 credit covers 3 hours of video/audio transcription. On a **cache miss**, one `standard_request` or `residential_request` is also charged for the metadata fetch (residential for Instagram, Facebook-watch, Rumble; standard otherwise), plus `transcription_hour` proportional to the audio duration. On a **cache hit** (same URL transcribed before, by any user), only `transcription_hour` is charged — the metadata fetch is skipped. Users with the `free_on_cache_hit` sponsorship flag get the cache-hit `transcription_hour` waived (`waived: true` in the per-charge entry). Set `include_usage: true` to receive a [`usage`](/api-reference/usage-costs#per-call-usage-disclosure) block with the breakdown. The async mode is billed exactly the same way. A `video_too_long` rejection on the synchronous endpoint still bills the metadata fetch that read the duration (`standard_request` or `residential_request`, not refunded) but no `transcription_hour`. ## Supported Platforms VidNavigator can generate transcripts from these platforms using speech-to-text: * Instagram * X / Twitter (just copy the tweet video link) * Facebook (public videos only) * TikTok * Dailymotion * Loom * Vimeo ## Request Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `video_url` | string | Yes | URL of the video to transcribe. For Instagram carousel posts, append `?img_index=N` to select a specific video. | | `transcript_text` | boolean | No | When `true`, returns the transcript as a single plain-text string instead of an array of segments. | | `all_videos` | boolean | No | For Instagram carousel posts only. When `true`, transcribes ALL videos in the post and returns them in an array. Ignored for non-carousel URLs (reels, TikTok, etc.). | ## Instagram Carousel Behavior Instagram **carousel posts** (e.g. `https://www.instagram.com/p/.../`) may contain multiple videos and/or images. * **Select a specific item**: Append `?img_index=N` to the URL to select a specific item (1-based).\ Example: `https://www.instagram.com/p/ABC123/?img_index=2` selects the **second** item. * **Transcribe all videos**: Set `all_videos=true` to transcribe **all videos** in the carousel. The response format changes (see “Success Response”). If a carousel post contains **only images** (no videos), the API returns `400` with `error=no_videos_found`. ## Example Usage ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcribe" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.instagram.com/reel/C86ZvEaqRmo/" }' ``` ```python Python theme={null} from vidnavigator import VidNavigatorClient, VidNavigatorError client = VidNavigatorClient() try: result = client.transcribe_video( video_url="https://www.instagram.com/reel/C86ZvEaqRmo/" ) print("Video Title:", result.data.video_info.title) for segment in result.data.transcript: print(f"[{segment.start:.2f}s - {segment.end:.2f}s] {segment.text}") except VidNavigatorError as e: print(f"An error occurred: {e.message}") ``` ```javascript JavaScript theme={null} const { VidNavigatorClient, VidNavigatorError } = require('vidnavigator'); const client = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY, }); async function transcribeVideo() { try { const { video_info, transcript } = await client.transcribeVideo({ video_url: "https://www.instagram.com/reel/C86ZvEaqRmo/" }); console.log("Video Title:", video_info.title); transcript.forEach(segment => { console.log(`[${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s] ${segment.text}`); }); } catch (error) { if (error instanceof VidNavigatorError) { console.error(`An error occurred: ${error.message}`); } else { console.error("An unexpected error occurred:", error); } } } transcribeVideo(); ``` ### Instagram Carousel (select a specific video with `?img_index=2`) ```bash theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcribe" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.instagram.com/p/ABC123/?img_index=2" }' ``` ### Instagram Carousel (transcribe all videos with `all_videos=true`) ```bash theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcribe" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.instagram.com/p/ABC123/", "all_videos": true }' ``` ## Success Response (200 OK) This endpoint has **two** success response shapes (a `oneOf`), depending on whether you set `all_videos=true`: * **Single-video response (default)**: returns `data.video_info` + `data.transcript` * **All-videos response (`all_videos=true`)**: returns `data.carousel_info` + `data.videos[]` `transcript_text=true` affects the shape of each returned `transcript` (string vs segments) in both response types. ### Single-video response (default) ```json theme={null} { "status": "success", "data": { "video_info": { "title": "A Reel Interesting Video", "description": "An example description for the video.", "thumbnail": "https://example.com/thumbnail.jpg", "url": "https://www.instagram.com/reel/C86ZvEaqRmo/", "channel": "Example Creator", "duration": 58.5, "views": 123456, "likes": 7890, "published_date": "2024-07-01", "keywords": ["example", "social media", "video"], "category": "Entertainment" }, "transcript": [ { "text": "This is the first sentence of the video.", "start": 0.5, "end": 3.2 }, { "text": "And this is the second.", "start": 3.5, "end": 5.1 } ] } } ``` ### All-videos response (`all_videos=true`) ```json theme={null} { "status": "success", "data": { "carousel_info": { "total_items": 5, "video_count": 2, "image_count": 3, "transcribed_count": 2, "total_duration": 123.4 }, "videos": [ { "index": 1, "status": "success", "video_info": { "title": "Video 1", "description": "Example description", "thumbnail": "https://example.com/thumb1.jpg", "url": "https://www.instagram.com/p/ABC123/?img_index=1", "channel": "Example Creator", "duration": 60.0, "views": 123, "likes": 45, "published_date": "2024-07-01", "keywords": ["example"], "category": "Entertainment" }, "transcript": [ { "text": "First line", "start": 0.0, "end": 2.0 } ] }, { "index": 2, "status": "error", "error": "transcript_not_available", "message": "Transcript could not be generated for this video." } ] } } ``` ## Error Responses | Status | `error` | Description | | - | - | - | | `400` | `missing_parameter`, `invalid_parameter`, `invalid_url`, `unsupported_url`, `unsupported_platform`, `request_body_required` | Invalid request. | | `400` | `no_videos_found` | The post carries no video (e.g. an image-only carousel). | | `400` | `video_too_long` | The media is longer than 10 minutes. Use [`POST /transcribe/async`](/api-reference/endpoint/transcribe-async). | | `402` | `limit_exceeded` | Not enough credits. | | `403` | `content_restricted` | The content requires login or age verification. | | `404` | `video_not_found`, `video_unavailable`, `transcript_not_available` | The video can't be found or accessed, or no transcript could be generated. | | `451` | `geo_restricted` | The content is not available in this region. | | `500` | `metadata_fetch_failed`, `audio_extraction_failed`, `transcription_failed`, `internal_server_error` | Processing failed. Charges are reverted. | See [Errors](/api-reference/errors) for the full list. # Transcribe Online Video (Async) Source: https://docs.vidnavigator.com/api-reference/endpoint/transcribe-async POST /transcribe/async Queue a speech-to-text transcription and return a `task_id` immediately. **Use this instead of `POST /transcribe` for anything longer than ~10 minutes.** A synchronous transcription holds the HTTP connection open for the whole download and speech-to-text pass; past roughly ten minutes of audio that starts failing against client timeouts, reverse proxies and marketplace gateways. This endpoint has no duration cap. Accepts the same body as `POST /transcribe`, plus `webhook_url`. **Billing** is identical to the synchronous endpoint and happens in the background worker. Because the `202` fires before any charge is made, `include_usage` is not accepted here — pass it on `GET /transcribe/{task_id}` instead, which replays the final charges once `task_status=completed`. A failed job has all of its charges reverted. See https://docs.vidnavigator.com/guides/async-jobs **Submit-time credit gate:** the submit endpoint returns `402` instead of a `task_id` when the account has less than 60 seconds of transcription credit left. This is a spam gate, not a quote — the real, duration-accurate charge happens when the job runs, and a job whose video exceeds the remaining balance still fails on the same credit check a synchronous call would hit. An invalid or deactivated API key is rejected with `401`, and a key without the endpoint's permission with `403`; no task is created in either case. Transcribe an online video of **any length**. The job runs in the background: you get a `task_id` immediately, then collect the transcript by polling or through a [webhook](/webhooks). Use this endpoint instead of [`POST /transcribe`](/api-reference/endpoint/transcribe) for media longer than **10 minutes**, which the synchronous endpoint rejects with `video_too_long`. It also works for short videos, so it is the safe default when you don't know the duration. ## How It Works `POST /transcribe/async` with the **same body** as [`POST /transcribe`](/api-reference/endpoint/transcribe), plus an optional `webhook_url`. You get `202 Accepted` with a `task_id` and a `check_status_url`. Poll `GET /transcribe/{task_id}` every few seconds while `task_status` is `processing` — polling is free — or receive a [webhook](/webhooks) when the job finishes. On `completed`, `data.result` is identical to the synchronous response's `data` block, so the same parsing code works for both. On `failed`, `data.error` carries the same error code the synchronous endpoint would have returned. ## Billing Billed **exactly like** [`POST /transcribe`](/api-reference/endpoint/transcribe) — the async mode costs nothing extra. Charges are made in the background worker, so pass `include_usage=true` on the result request (not on the submit) to see them. A failed job has all of its charges reverted. ## Submit the job `POST https://api.vidnavigator.com/v1/transcribe/async` | Parameter | Type | Required | Description | | - | - | - | - | | `video_url` | string | Yes | URL of the video to transcribe. For Instagram carousel posts, append `?img_index=N` to select a specific video. | | `transcript_text` | boolean | No | When `true`, returns the transcript as a single plain-text string. | | `all_videos` | boolean | No | For Instagram carousel posts only. When `true`, transcribes all videos in the post. | | `webhook_url` | string | No | Where to POST the result when the job finishes. Overrides the account default set in [Studio → API](https://vidnavigator.com/studio/api); pass `""` to opt this job out of the default. Must be a public `https` URL. See [Webhooks](/webhooks). | `include_usage` is not accepted on the submit request — pass it on the poll request instead. ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcribe/async" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.instagram.com/reel/C86ZvEaqRmo/", "webhook_url": "https://example.com/hooks/vidnavigator" }' ``` ```python Python theme={null} import time import requests BASE = "https://api.vidnavigator.com" HEADERS = {"X-API-Key": "YOUR_API_KEY"} task = requests.post( f"{BASE}/v1/transcribe/async", headers=HEADERS, json={"video_url": "https://www.instagram.com/reel/C86ZvEaqRmo/"}, ).json()["data"] while True: job = requests.get(BASE + task["check_status_url"], headers=HEADERS).json()["data"] if job["task_status"] != "processing": break time.sleep(5) if job["task_status"] == "completed": for segment in job["result"]["transcript"]: print(f'[{segment["start"]:.2f}s - {segment["end"]:.2f}s] {segment["text"]}') else: print("Failed:", job["error"]["error"], job["error"]["message"]) ``` ```javascript JavaScript theme={null} const BASE = 'https://api.vidnavigator.com'; const HEADERS = { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; const { data: task } = await (await fetch(`${BASE}/v1/transcribe/async`, { method: 'POST', headers: HEADERS, body: JSON.stringify({ video_url: 'https://www.instagram.com/reel/C86ZvEaqRmo/' }) })).json(); let job; do { await new Promise(r => setTimeout(r, 5000)); ({ data: job } = await (await fetch(BASE + task.check_status_url, { headers: HEADERS })).json()); } while (job.task_status === 'processing'); if (job.task_status === 'completed') { job.result.transcript.forEach(s => console.log(`[${s.start}s] ${s.text}`)); } else { console.error('Failed:', job.error.error, job.error.message); } ``` ### Response (202 Accepted) ```json theme={null} { "status": "success", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "task_status": "processing", "job_type": "transcribe", "expires_at": "2026-09-23T13:00:00Z", "check_status_url": "/v1/transcribe/550e8400-e29b-41d4-a716-446655440000", "webhook_url": "https://example.com/hooks/vidnavigator", "message": "Transcription job accepted." } } ``` | Status | `error` | Description | | - | - | - | | `400` | `missing_parameter`, `invalid_parameter`, … | Invalid request. | | `401` | — | Invalid or deactivated API key. | | `402` | `limit_exceeded` | Less than 60 seconds of transcription credit left. No task is created. | | `403` | — | The API key lacks permission for this endpoint. | | `429` | `too_many_active_jobs` | Too many jobs already running for this account. | ## Get the result The playground above only covers the submit request. Call the result endpoint with cURL or your HTTP client. `GET https://api.vidnavigator.com/v1/transcribe/{task_id}` | Parameter | In | Required | Description | | - | - | - | - | | `task_id` | path | Yes | The `task_id` returned by `POST /transcribe/async`. | | `include_usage` | query | No | When `true` and `task_status=completed`, attaches a `usage` block with the final charges. | Polling is **free**. Poll every few seconds while `task_status` is `processing`. Results are kept for **1 hour after the job finishes**, and reading a task doesn't delete it. ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/transcribe/550e8400-e29b-41d4-a716-446655440000?include_usage=true" \ -H "X-API-Key: YOUR_API_KEY" ``` On `completed`, `data.result` is **identical** to the `data` block of the [synchronous response](/api-reference/endpoint/transcribe) (`video_info` + `transcript`, or `carousel_info` + `videos` when `all_videos=true`). ```json theme={null} { "status": "success", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "task_status": "completed", "job_type": "transcribe", "request": { "video_url": "https://www.instagram.com/reel/C86ZvEaqRmo/", "transcript_text": false, "all_videos": false }, "created_at": "2026-09-23T12:00:00Z", "started_at": "2026-09-23T12:00:01Z", "completed_at": "2026-09-23T12:00:40Z", "expires_at": "2026-09-23T13:00:40Z", "check_status_url": "/v1/transcribe/550e8400-e29b-41d4-a716-446655440000", "webhook": { "status": "delivered", "attempts": 1, "response_status": 200, "last_error": null, "delivered_at": "2026-09-23T12:00:41Z" }, "result": { "video_info": { "title": "A Reel Interesting Video", "duration": 58.5, "url": "https://www.instagram.com/reel/C86ZvEaqRmo/" }, "transcript": [ { "text": "This is the first sentence of the video.", "start": 0.5, "end": 3.2 } ] }, "error": null } } ``` On `failed`, `data.result` is `null` and `data.error` carries the same code the synchronous endpoint would have returned, e.g. `{ "error": "transcript_not_available", "message": "...", "http_status": 404 }`. All charges are reverted. | Status | `error` | Description | | - | - | - | | `404` | `task_not_found` | Unknown `task_id`, expired, owned by another account, or not a transcription task. | ## Webhook Pass `webhook_url` on the submit request, or configure a default endpoint in [Studio → API](https://vidnavigator.com/studio/api), to be called back when the job finishes. See [Webhooks](/webhooks) for the payload and signature verification, and [Async Jobs](/guides/async-jobs) for the full workflow. # Get Video Transcript Source: https://docs.vidnavigator.com/api-reference/endpoint/transcript POST /transcript Extract transcript from any supported online video (YouTube, Vimeo, Twitter/X, TikTok, Facebook, Dailymotion, Loom, etc.) with optional language selection. The endpoint auto-detects the platform from the URL and routes internally. The URL inside `video_url` is what determines the upstream behavior. **Note:** for Instagram videos, use `/transcribe` instead (speech-to-text). Options: - `transcript_text=true`: Returns the transcript as a single plain-text string instead of an array of segments. - `metadata_only=true`: Returns only video metadata (no transcript). - `fallback_to_metadata=true`: If transcript is unavailable, returns video metadata with an empty transcript instead of a 404 error (ignored when `metadata_only=true`). **Billing** depends on the URL: - YouTube (full transcript OR `metadata_only=true`) → 1× `residential_request` (the transcript fetch routes through the residential proxy) - Non-YouTube → 1× `standard_request` (some platforms internally use residential and would be billed accordingly) The charge applies whether or not a transcript is returned (mirrors the proxy hop we paid for). Set `include_usage=true` to receive a `usage` block in the response. Extract accurate metadata and transcripts from any supported online video — YouTube, TikTok, Facebook, X/Twitter, Vimeo, Dailymotion, Loom, and more — through a single endpoint. This is now the **single transcript endpoint** for every supported platform, including YouTube. The endpoint auto-detects the platform from `video_url` and routes internally. (The old `/youtube/transcript` endpoint has been merged into `/transcript`.) **For Instagram videos**, use the [/transcribe](/api-reference/endpoint/transcribe) endpoint instead (speech-to-text). ## Billing Billing depends on the URL: * **YouTube** (full transcript **or** `metadata_only: true`) → 1× `residential_request` (the fetch routes through the residential proxy). * **Non-YouTube** → 1× `standard_request` (some platforms internally require residential and are billed as `residential_request` accordingly). The charge applies whether or not a transcript is returned, mirroring the proxy hop that was paid for. Set `include_usage: true` to receive a [`usage`](/api-reference/usage-costs#per-call-usage-disclosure) block in the response. ## Overview This endpoint automatically extracts rich video metadata and transcripts from online videos with high accuracy. ## Supported Platforms VidNavigator can retrieve transcripts from a wide variety of sources. Below is the list of platforms currently supported by this endpoint: * YouTube (uses residential proxy infrastructure for reliable access) * X / Twitter (just copy the tweet video link) * Facebook (public videos only) * TikTok * Dailymotion * Loom * Vimeo **For Instagram videos**, use the [/transcribe](/api-reference/endpoint/transcribe) endpoint instead, which runs speech-to-text. ## Use Cases Extract transcripts for AI analysis, sentiment analysis, or content categorization Enable text-based search across video content libraries Generate captions and transcripts for accessibility compliance Create summaries, articles, or repurpose video content ## Request Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `video_url` | string | Yes | URL of the video to retrieve the transcript for (YouTube or any other supported platform) | | `language` | string | No | ISO2 language code (e.g., "en", "es", "fr") | | `metadata_only` | boolean | No | When `true`, returns only video metadata without fetching the transcript. Takes precedence over `fallback_to_metadata`. Usage is still recorded. | | `fallback_to_metadata` | boolean | No | When `true`, returns video metadata with an empty transcript if transcript is unavailable (returns 200 instead of 404). Ignored if `metadata_only` is `true`. | | `transcript_text` | boolean | No | When `true`, returns the transcript as a single plain-text string instead of an array of segments. | | `include_usage` | boolean | No | When `true`, the response includes a `usage` block listing every meter charged during this request, the total credits deducted, and the remaining balance. | ## Example Usage ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcript" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ", "language": "en" }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/transcript" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "video_url": "https://youtube.com/watch?v=dQw4w9WgXcQ", "language": "en" } response = requests.post(url, headers=headers, json=data) result = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/transcript', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ', language: 'en' }) }); const result = await response.json(); ``` ### Transcript as Plain Text (`transcript_text=true`) Use `transcript_text=true` to return the transcript as a single plain-text string instead of an array of `{ text, start, end }` segments: ```bash theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcript" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://twitter.com/user/status/123456789", "transcript_text": true }' ``` ### Getting Metadata Only If you only need video metadata without the transcript (faster and still counts as usage): ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcript" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.tiktok.com/@user/video/123456789", "metadata_only": true }' ``` ```python Python theme={null} data = { "video_url": "https://www.tiktok.com/@user/video/123456789", "metadata_only": True } response = requests.post(url, headers=headers, json=data) ``` ```javascript JavaScript theme={null} const result = await fetch('https://api.vidnavigator.com/v1/transcript', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ video_url: 'https://www.tiktok.com/@user/video/123456789', metadata_only: true }) }); ``` For YouTube, `metadata_only: true` still routes through the residential proxy and is billed as a `residential_request`. ### Getting Usage Disclosure (`include_usage=true`) Set `include_usage: true` to receive a `usage` block describing exactly what was charged: ```json theme={null} { "status": "success", "data": { "video_info": { ... }, "transcript": [ ... ] }, "usage": { "charges": [ { "service_type": "residential_request", "quantity": 1, "credits": 0.005 } ], "total_credits": 0.005 } } ``` ## Limitations * `https://fb.watch/` format is not supported, use the `https://www.facebook.com/watch/?v=` format instead. ## Troubleshooting * **Invalid URL**: Ensure the video URL is complete and valid. This endpoint supports direct links from YouTube, X (a tweet), Vimeo, TikTok, etc. * **Transcript Not Found**: Please ensure that the video has subtitles available (either manually added or auto-generated). If no subtitles are present, the transcript cannot be extracted. Consider using `fallback_to_metadata: true` to get metadata even when transcript is unavailable. * **Language Not Supported**: Sometimes the selected subtitles language is not available. In which case, you will get a clear message stating that. Verify that the chosen language is available for the video. We recommend not using the language input if you are unsure. * **For Instagram videos**: This endpoint does not transcribe Instagram. Please use the [/transcribe](/api-reference/endpoint/transcribe) endpoint instead. If issues persist or need more help, please reach out to us at [support@vidnavigator.com](mailto:support@vidnavigator.com). ## Success Response (200 OK) Returns a `video_info` object and a `transcript` object. ```json theme={null} { "status": "success", "data": { "video_info": { "title": "A Really Cool Video", "description": "An example description for the video.", "thumbnail": "https://example.com/thumbnail.jpg", "url": "https://twitter.com/user/status/123456789", "channel": "Example Creator", "duration": 45.5, "views": 123456, "likes": 9876, "published_date": "2024-01-15", "keywords": ["example", "twitter", "video"], "category": "Entertainment" }, "transcript": [ { "text": "This is the first sentence of the video.", "start": 0.5, "end": 3.2 }, { "text": "And this is the second sentence.", "start": 3.5, "end": 5.1 } ] } } ``` ### Response with `transcript_text: true` When you set `transcript_text: true`, the transcript is returned as a single string: ```json theme={null} { "status": "success", "data": { "video_info": { ... }, "transcript": "This is the first sentence of the video. And this is the second sentence." } } ``` ### Response with `metadata_only: true` When you set `metadata_only: true`, only the video metadata is returned: ```json theme={null} { "status": "success", "data": { "video_info": { "title": "A Really Cool Video", "description": "An example description for the video.", "thumbnail": "https://example.com/thumbnail.jpg", "url": "https://twitter.com/user/status/123456789", "channel": "Example Creator", "duration": 45.5, "views": 123456, "likes": 9876, "published_date": "2024-01-15" } } } ``` # Tweet Claim Analysis Source: https://docs.vidnavigator.com/api-reference/endpoint/tweet-claim-analysis POST /tweet/statement Fetches the tweet content (including any quoted tweet and attached media), then uses AI to extract a rich structured analysis: the core claim, classification axes, topics, entities, and raw tweet data. **Multimodal media handling:** attached media is processed by type. Videos are transcribed (platform captions or speech-to-text) and summarized; images are described by a vision model — transcribed text, chart/graph data points, recognisable people and logos, and any claims they make — and folded into the analysis. **Classification axes** (`claim_type`, `intent`, `tone`, `emotion`, `authority`) reflect **only the original tweet text**, not quoted content, media, or images. **Billing (consolidated, mirrors `/v1/analyze/video`):** - exactly one `analysis_request` charge with `quantity = ceil(total_tokens / 15000)`, summing tokens across the optional per-media extraction calls, the optional image vision calls, AND the main statement-extraction call. A typical tweet+quoted tweet with one video or an attached image runs several LLM calls but produces just one billing entry when the combined tokens stay under 15k. Image analysis is billed through this same `analysis_request` meter. - one `standard_request` per media URL whose transcript or metadata is fetched (mirrors `/v1/transcript`). - `transcription_hour` proportional to the audio duration if speech-to-text runs for a media video that has no platform transcript. Waived for users with `users.free_on_cache_hit=true` when STT was served from cache (same waiver as `/v1/transcribe`). **Long videos:** this endpoint is synchronous and holds the connection open for the whole transcription. For media longer than ~10 minutes use `POST /tweet/statement/async` instead, which returns a `task_id` and can call your webhook when it finishes. Media longer than 10 minutes is rejected here with `video_too_long` (HTTP 400). The metadata fetch needed to read the video's duration is billed (`standard_request` or `residential_request`) and is **not** refunded: the limit is documented, so the proxy call was spent answering a request that could not be served. No `transcription_hour` is charged — no audio is processed. Send long media to the async endpoint, which has no cap. See https://docs.vidnavigator.com/guides/async-jobs Extract the real meaning behind an X/Twitter post by analyzing the original tweet, any quoted tweet, and media shared in both contexts. ## Overview This endpoint turns a single X post into a machine-readable intelligence object. It does not just retrieve tweet text or metadata. It analyzes what the author actually meant by combining the original tweet, any quoted tweet, and any media attached to either one. That means the output reflects the full communicative intent of the post, not just the visible text. If a tweet makes sense only when read together with a quoted post, an attached video, or media inside the quoted tweet, this endpoint includes that context in the analysis. It is designed for workflows that need more than keyword monitoring or tweet retrieval, such as fact-checking pipelines, narrative tracking, social risk monitoring, and structured ingestion into dashboards or AI systems. This is **not** a basic tweet retrieval API. Its core value is intelligence extraction: understanding the author's intent, the role of the quoted tweet, and the meaning conveyed by media attached to both the original and quoted post. ### What This Endpoint Returns Extract a multi-sentence `final_statement` that reflects what the author actually meant, not just what the raw tweet text says in isolation. Incorporate quoted tweet context when the original post depends on it for meaning, stance, or narrative framing. Classify the original tweet by `claim_type`, `intent`, `tone`, `emotion`, and `authority`. Analyze media attached to the tweet and the quoted tweet. Videos are transcribed (platform captions or speech-to-text) and summarized; images are described by a vision model — transcribed text, chart/graph data points, recognizable people and logos, and any claims they make. ### Why This Endpoint Is Different Most tweet APIs stop at text retrieval, engagement metrics, or shallow metadata. This endpoint is built for intelligence extraction. * It understands the **intent** behind the original tweet. * It understands the **quoted tweet** as part of the message, not as unrelated metadata. * It analyzes **shared media in the original tweet** — both videos (transcribed) and images (described by a vision model). * It analyzes **shared media in the quoted tweet**. * It returns a structured output that reflects the **combined meaning** of all of that context. This is especially important for X posts where the author's real point is only clear when you interpret the text together with a quoted post, a reaction clip, a narrated video, or media evidence embedded in either layer. ### How It Works 1. Send a `tweet_id` for the target X post. 2. VidNavigator fetches the original tweet, any quoted tweet, and media attached to both. 3. If video or audio is present in either context, the system retrieves transcripts or falls back to speech-to-text when needed. Attached images are described by a vision model. 4. The combined context is analyzed to determine the real claim, intent, framing, and supporting media meaning. 5. The result is returned as structured JSON ready for applications and pipelines. This endpoint accepts a **tweet ID**, not a full URL. For example, from `https://x.com/user/status/1912345678901234567`, the `tweet_id` is `1912345678901234567`. ## Synchronous or async? Attached videos are transcribed in full before the claim can be extracted, so a tweet carrying a long video takes as long as that video. This endpoint therefore comes in two modes, with the same request body and the same result: | Mode | Endpoint | Attached video duration | How you get the result | | - | - | - | - | | Synchronous | `POST /tweet/statement` | **Up to 10 minutes** | In the HTTP response | | Async | [`POST /tweet/statement/async`](/api-reference/endpoint/tweet-claim-analysis-async) | **Any length** | Poll `GET /tweet/statement/{task_id}` or receive a [webhook](/webhooks) | **When the tweet (or the tweet it quotes) carries a video longer than 10 minutes, the synchronous endpoint rejects it** with `400 video_too_long`. Use the [async mode](/api-reference/endpoint/tweet-claim-analysis-async) — it also works for text-only tweets and short clips. ## Classification Axes The classification labels below are derived from the **original tweet text only**, not from quoted tweets or attached media: * `claim_type`: `factual_claim`, `opinion`, `question`, `call_to_action`, `satire`, `news_sharing`, `personal_experience` * `intent`: `educate`, `inform`, `analyze`, `persuade`, `entertain`, `inspire`, `challenge` * `tone`: `serious`, `humorous`, `provocative`, `neutral`, `warm`, `skeptical`, `inspirational` * `emotion`: `curiosity`, `urgency`, `outrage`, `fear`, `hope_inspiration`, `confidence_reassurance`, `empathy_warmth`, `awe_wonder` * `authority`: `data_driven`, `expert_led`, `experience_based`, `speculative` ## Example Usage ### Best For Use this endpoint when you need to understand what a post is really saying, especially when meaning depends on: * the author's underlying intent * a quoted tweet that changes the framing * attached video or audio in the original tweet * attached video or audio in the quoted tweet If you only need raw tweet metadata, this is more powerful than necessary. This endpoint is for interpretation and intelligence extraction. ### Basic Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tweet/statement" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tweet_id": "1912345678901234567" }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/tweet/statement" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "tweet_id": "1912345678901234567" } response = requests.post(url, headers=headers, json=data) result = response.json() print("Final statement:", result["data"]["final_statement"]) print("Claim type:", result["data"]["claim_type"]) print("Topics:", result["data"]["topics"]) ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/tweet/statement', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ tweet_id: '1912345678901234567' }) }); const result = await response.json(); console.log(result.data.final_statement); ``` ### Example: Feeding Results into Search or Verification When you want to take a tweet's interpreted meaning and use it for search, fact-checking, or evidence retrieval, the structured output is ready to plug into downstream pipelines: ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tweet/statement" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tweet_id": "1912345678901234567" }' ``` Once returned, you can use: * `final_statement` for human review or as a search/verification query * `detailed_analysis` for framing and stance context * `topics` and `entities` for clustering, tagging, or analytics ## Response Example ```json theme={null} { "status": "success", "data": { "final_statement": "The author claims that a new city policy will ban gas-powered delivery scooters in the downtown area next month, arguing that the change is being rushed without enough support for affected workers.", "detailed_analysis": "The tweet presents a concrete policy claim and frames it as both imminent and harmful to delivery workers. The author positions the decision as poorly planned and emphasizes the lack of transition support. The message appears intended to persuade the audience that the policy is unfair rather than merely to report it. The framing combines factual assertion with rhetorical pressure.", "topics": [ "urban policy", "delivery workers", "transport regulation", "labor impact" ], "entities": [ "City Council", "downtown" ], "claim_type": "factual_claim", "intent": "persuade", "tone": "provocative", "emotion": "urgency", "authority": "speculative", "tweet_text": "Next month the city is banning gas scooters downtown and nobody has explained how delivery workers are supposed to adapt this fast.", "tweet_media_summary": "A short attached video shows several delivery riders speaking about costs, route changes, and reduced earnings if the rule takes effect.", "quoted_tweet_text": "Official announcement: downtown clean transport rules begin next month.", "quoted_media_summary": null } } ``` ## Field Guide | Field | Description | | - | - | | `final_statement` | Full normalized claim in the author's voice, informed by the full contextual analysis | | `detailed_analysis` | 3-5 sentence explanation covering claim, framing, evidence, and stance | | `topics` | Key subjects discussed in the tweet (3-8) | | `entities` | Named entities such as people, organizations, or places (0-8) | | `claim_type` / `intent` / `tone` / `emotion` / `authority` | Classification axes derived from the **original tweet text only** | | `tweet_text` | Original text of the source tweet | | `tweet_media_summary` | Full content summary of media attached to the original tweet: a video transcript summary **or** a vision-model description of attached image(s). `null` when no media is attached. | | `quoted_tweet_text` | Text of a quoted or referenced tweet, when present | | `quoted_media_summary` | Full content summary of media attached to the quoted tweet (video transcript or image description). `null` when no media is attached. | ## Intelligence Model This endpoint should be thought of as a multi-layer interpretation pipeline: 1. Read the original tweet text. 2. Detect whether a quoted tweet changes or completes the meaning. 3. Analyze media attached to the original tweet. 4. Analyze media attached to the quoted tweet. 5. Produce a final claim and classification that reflect the author's intended message as completely as possible. That is why the endpoint is effective for tweets where the visible text alone is incomplete, sarcastic, reactive, or dependent on embedded media. ## Billing Notes Billing is **consolidated** and mirrors [`/analyze/video`](/api-reference/endpoint/analyze-video). A single call can involve several internal LLM calls (media extraction, image vision, and the main statement extraction) but produces **one** billing entry per meter: * **Exactly one `analysis_request` charge** with `quantity = ceil(total_tokens / 15000)`, summing tokens across the optional per-media extraction calls, the optional image vision calls, and the main statement-extraction call. A typical tweet + quoted tweet with one video or one attached image runs several LLM calls but produces just one billing entry when the combined tokens stay under 15k. **Image analysis is billed through this same `analysis_request` meter.** * **One `standard_request` per media URL** whose transcript or metadata is fetched (mirrors [`/transcript`](/api-reference/endpoint/transcript)). * **`transcription_hour`** proportional to the audio duration if speech-to-text runs for a media video with no platform transcript. Waived for users with `free_on_cache_hit=true` when STT was served from cache. For cost planning, a text-only tweet is typically simpler than a tweet with embedded media and a quoted post that also contains video or images. The async mode is billed exactly the same way. A `video_too_long` rejection on the synchronous endpoint still bills the metadata fetch that read the duration (not refunded), but no `transcription_hour`. ## Use Cases Convert noisy social posts into verification-ready claims that analysts can review quickly. Group similar claims using `final_statement`, `topics`, and `entities` across many tweets. Detect accusations, emotionally charged narratives, and emerging risk signals earlier. Feed structured social intelligence into internal tools, LLM workflows, or search systems. ## Troubleshooting * **Invalid tweet ID**: Make sure you are sending only the numeric post ID, not the full X URL. * **Upstream fetch failure**: If X content cannot be retrieved or parsed, the endpoint may return a `502` response. * **Missing media summary**: If no media is attached, or media could not be processed, `tweet_media_summary` and `quoted_media_summary` may be `null`. * **Unexpected classification**: `claim_type`, `intent`, `tone`, `emotion`, and `authority` are based only on the original tweet text, while `final_statement` and media summaries reflect the broader contextual analysis. * **`video_too_long`**: the tweet or its quoted tweet carries a video longer than 10 minutes. Use [`POST /tweet/statement/async`](/api-reference/endpoint/tweet-claim-analysis-async). * **`402 limit_exceeded`**: not enough credits. See [Errors](/api-reference/errors) for the full list. # Tweet Claim Analysis (Async) Source: https://docs.vidnavigator.com/api-reference/endpoint/tweet-claim-analysis-async POST /tweet/statement/async Queue a tweet claim analysis and return a `task_id` immediately. **Use this instead of `POST /tweet/statement` when the tweet (or the tweet it quotes) carries a long video.** That media is transcribed in full before the claim can be extracted, so the request inherits the same timeout problem as `/transcribe`. Accepts the same body as `POST /tweet/statement`, plus `webhook_url`. **Billing** is identical to the synchronous endpoint and happens in the background. Pass `include_usage=true` on `GET /tweet/statement/{task_id}` for the breakdown. See https://docs.vidnavigator.com/guides/async-jobs **Submit-time credit gate:** the submit endpoint returns `402` instead of a `task_id` when the account has less than 60 seconds of transcription credit left. This is a spam gate, not a quote — the real, duration-accurate charge happens when the job runs, and a job whose video exceeds the remaining balance still fails on the same credit check a synchronous call would hit. An invalid or deactivated API key is rejected with `401`, and a key without the endpoint's permission with `403`; no task is created in either case. Analyze a tweet's claim when it carries a **long video**. The job runs in the background: you get a `task_id` immediately, then collect the result by polling or through a [webhook](/webhooks). Use this endpoint instead of [`POST /tweet/statement`](/api-reference/endpoint/tweet-claim-analysis) when the tweet (or the tweet it quotes) may carry a video longer than **10 minutes** — attached media is transcribed in full before the claim is extracted, and the synchronous endpoint rejects it with `video_too_long`. It works for any tweet, with or without media. ## How It Works `POST /tweet/statement/async` with the **same body** as [`POST /tweet/statement`](/api-reference/endpoint/tweet-claim-analysis), plus an optional `webhook_url`. You get `202 Accepted` with a `task_id` and a `check_status_url`. Poll `GET /tweet/statement/{task_id}` every few seconds while `task_status` is `processing` — polling is free — or receive a [webhook](/webhooks) when the job finishes. On `completed`, `data.result` is identical to the synchronous response's `data` block, so the same parsing code works for both. On `failed`, `data.error` carries the same error code the synchronous endpoint would have returned. ## Billing Billed **exactly like** [`POST /tweet/statement`](/api-reference/endpoint/tweet-claim-analysis) — the async mode costs nothing extra. Charges are made in the background worker, so pass `include_usage=true` on the result request (not on the submit) to see them. A failed job has all of its charges reverted. ## Submit the job `POST https://api.vidnavigator.com/v1/tweet/statement/async` | Parameter | Type | Required | Description | | - | - | - | - | | `tweet_id` | string | Yes | The X/Twitter tweet ID. | | `webhook_url` | string | No | Where to POST the result when the job finishes. Overrides the account default set in [Studio → API](https://vidnavigator.com/studio/api); pass `""` to opt this job out of the default. Must be a public `https` URL. See [Webhooks](/webhooks). | ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tweet/statement/async" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tweet_id": "1912345678901234567", "webhook_url": "https://example.com/hooks/vidnavigator" }' ``` ```python Python theme={null} import time import requests BASE = "https://api.vidnavigator.com" HEADERS = {"X-API-Key": "YOUR_API_KEY"} task = requests.post( f"{BASE}/v1/tweet/statement/async", headers=HEADERS, json={"tweet_id": "1912345678901234567"}, ).json()["data"] while True: job = requests.get(BASE + task["check_status_url"], headers=HEADERS).json()["data"] if job["task_status"] != "processing": break time.sleep(5) if job["task_status"] == "completed": print(job["result"]["final_statement"]) else: print("Failed:", job["error"]["error"], job["error"]["message"]) ``` ```javascript JavaScript theme={null} const BASE = 'https://api.vidnavigator.com'; const HEADERS = { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }; const { data: task } = await (await fetch(`${BASE}/v1/tweet/statement/async`, { method: 'POST', headers: HEADERS, body: JSON.stringify({ tweet_id: '1912345678901234567' }) })).json(); let job; do { await new Promise(r => setTimeout(r, 5000)); ({ data: job } = await (await fetch(BASE + task.check_status_url, { headers: HEADERS })).json()); } while (job.task_status === 'processing'); console.log(job.task_status === 'completed' ? job.result.final_statement : job.error); ``` ### Response (202 Accepted) ```json theme={null} { "status": "success", "data": { "task_id": "9b2f1c3e-5d4a-4e8b-a1c2-3d4e5f6a7b8c", "task_status": "processing", "job_type": "tweet_statement", "expires_at": "2026-09-23T13:00:00Z", "check_status_url": "/v1/tweet/statement/9b2f1c3e-5d4a-4e8b-a1c2-3d4e5f6a7b8c", "webhook_url": "https://example.com/hooks/vidnavigator", "message": "Tweet analysis job accepted." } } ``` | Status | `error` | Description | | - | - | - | | `400` | `missing_parameter`, `invalid_parameter`, … | Invalid request. | | `401` | — | Invalid or deactivated API key. | | `402` | `limit_exceeded` | Less than 60 seconds of transcription credit left. No task is created. | | `403` | — | The API key lacks permission for this endpoint. | | `429` | `too_many_active_jobs` | Too many jobs already running for this account. | ## Get the result The playground above only covers the submit request. Call the result endpoint with cURL or your HTTP client. `GET https://api.vidnavigator.com/v1/tweet/statement/{task_id}` | Parameter | In | Required | Description | | - | - | - | - | | `task_id` | path | Yes | The `task_id` returned by `POST /tweet/statement/async`. | | `include_usage` | query | No | When `true` and `task_status=completed`, attaches a `usage` block with the final charges. | Polling is **free**. Results are kept for **1 hour after the job finishes**, and reading a task doesn't delete it. On `completed`, `data.result` is **identical** to the `data` block of the [synchronous response](/api-reference/endpoint/tweet-claim-analysis) (`final_statement`, `detailed_analysis`, `topics`, `entities`, classification axes and media summaries). ```json theme={null} { "status": "success", "data": { "task_id": "9b2f1c3e-5d4a-4e8b-a1c2-3d4e5f6a7b8c", "task_status": "completed", "job_type": "tweet_statement", "created_at": "2026-09-23T12:00:00Z", "started_at": "2026-09-23T12:00:01Z", "completed_at": "2026-09-23T12:06:12Z", "expires_at": "2026-09-23T13:06:12Z", "check_status_url": "/v1/tweet/statement/9b2f1c3e-5d4a-4e8b-a1c2-3d4e5f6a7b8c", "webhook": { "status": "delivered", "attempts": 1, "response_status": 200, "last_error": null, "delivered_at": "2026-09-23T12:06:13Z" }, "result": { "final_statement": "The author claims that ...", "detailed_analysis": "...", "topics": ["urban policy"], "entities": ["City Council"], "claim_type": "factual_claim", "intent": "persuade", "tone": "provocative", "emotion": "urgency", "authority": "speculative", "tweet_text": "...", "tweet_media_summary": "...", "quoted_tweet_text": null, "quoted_media_summary": null }, "error": null } } ``` On `failed`, `data.result` is `null` and `data.error` carries the same code and `http_status` the synchronous endpoint would have returned. All charges are reverted. | Status | `error` | Description | | - | - | - | | `404` | `task_not_found` | Unknown `task_id`, expired, owned by another account, or not a tweet analysis task. | ## Webhook Pass `webhook_url` on the submit request, or configure a default endpoint in [Studio → API](https://vidnavigator.com/studio/api), to be called back when the job finishes. See [Webhooks](/webhooks) for the payload and signature verification, and [Async Jobs](/guides/async-jobs) for the full workflow. # Update File Namespaces Source: https://docs.vidnavigator.com/api-reference/endpoint/update-file-namespaces PUT /file/{file_id}/namespaces Set the namespaces a file belongs to. Replaces any existing namespace assignments. The 'default' namespace is always preserved. Set which namespaces a file belongs to. This **replaces** any existing namespace assignments for the file. The `default` namespace is always preserved. ## Overview Use this endpoint to move a file between namespaces or assign it to multiple namespaces at once. The response returns the updated `namespace_ids` and resolved `namespaces` reflecting the new assignments. ## Example Request ```bash cURL theme={null} curl -X PUT "https://api.vidnavigator.com/v1/file/file_abc123/namespaces" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "namespace_ids": ["64a1b2c3d4e5f6789abc0002", "64a1b2c3d4e5f6789abc0003"] }' ``` ```python Python theme={null} import requests file_id = "file_abc123" url = f"https://api.vidnavigator.com/v1/file/{file_id}/namespaces" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "namespace_ids": ["64a1b2c3d4e5f6789abc0002", "64a1b2c3d4e5f6789abc0003"] } response = requests.put(url, headers=headers, json=data) result = response.json() ``` ```javascript JavaScript theme={null} const fileId = 'file_abc123'; const response = await fetch(`https://api.vidnavigator.com/v1/file/${fileId}/namespaces`, { method: 'PUT', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ namespace_ids: ['64a1b2c3d4e5f6789abc0002', '64a1b2c3d4e5f6789abc0003'] }) }); const result = await response.json(); ``` ## Success Response ```json theme={null} { "status": "success", "message": "File namespaces updated successfully", "data": { "namespace_ids": ["64a1b2c3d4e5f6789abc0001", "64a1b2c3d4e5f6789abc0002", "64a1b2c3d4e5f6789abc0003"], "namespaces": [ { "id": "64a1b2c3d4e5f6789abc0001", "name": "default" }, { "id": "64a1b2c3d4e5f6789abc0002", "name": "Client Calls" }, { "id": "64a1b2c3d4e5f6789abc0003", "name": "Product Demos" } ] } } ``` The `default` namespace is always preserved in the response, even if not explicitly included in `namespace_ids`. # Upload File Source: https://docs.vidnavigator.com/api-reference/endpoint/upload-file POST /upload/file Upload a new audio or video file for analysis and start processing. **Supported formats:** - Video: mp4, webm, mov, avi, wmv, flv, mkv - Audio: m4a, mp3, mpeg, mpga, wav **Processing options:** - `wait_for_completion=false` (default): Returns immediately, processing happens in background - `wait_for_completion=true`: Waits for complete processing before returning response Upload audio or video files for transcription and analysis. ## Overview Upload your own audio or video files to VidNavigator for processing. The API supports a wide range of formats and provides flexible processing options. ### Supported Formats mp4, webm, mov, avi, wmv, flv, mkv m4a, mp3, mpeg, mpga, wav ### Processing Options * **Asynchronous** (default): Returns immediately, processing happens in background * **Synchronous**: Waits for complete processing before returning response ## Billing File processing consumes `transcription_hour` usage for speech-to-text. 1 credit covers 3 hours of video/audio transcription. Uploaded files also count toward your storage quota. ### Namespace Assignment You can assign the uploaded file to one or more namespaces by including `namespace_ids` in the form data. This accepts a comma-separated string or a JSON array (e.g., `'["ns1","ns2"]'`). See [Namespaces](/api-reference/endpoint/list-namespaces) for managing namespaces. ## Example Usage ```bash cURL (Async) theme={null} curl -X POST "https://api.vidnavigator.com/v1/upload/file" \ -H "X-API-Key: YOUR_API_KEY" \ -F "file=@/path/to/your/video.mp4" \ -F "wait_for_completion=false" ``` ```bash cURL (Sync) theme={null} curl -X POST "https://api.vidnavigator.com/v1/upload/file" \ -H "X-API-Key: YOUR_API_KEY" \ -F "file=@/path/to/your/video.mp4" \ -F "wait_for_completion=true" ``` ```bash cURL (with namespace) theme={null} curl -X POST "https://api.vidnavigator.com/v1/upload/file" \ -H "X-API-Key: YOUR_API_KEY" \ -F "file=@/path/to/your/video.mp4" \ -F "namespace_ids=64a1b2c3d4e5f6789abc0002" ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/upload/file" headers = {"X-API-Key": "YOUR_API_KEY"} with open("video.mp4", "rb") as f: files = {"file": f} data = {"wait_for_completion": "false"} response = requests.post(url, headers=headers, files=files, data=data) result = response.json() print(f"File ID: {result['file_id']}") ``` ```javascript JavaScript theme={null} const formData = new FormData(); formData.append('file', fileInput.files[0]); formData.append('wait_for_completion', 'false'); const response = await fetch('https://api.vidnavigator.com/v1/upload/file', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY' }, body: formData }); const result = await response.json(); ``` ## Response Examples All response variants include `data.file_info` with `namespace_ids` and `namespaces` reflecting the file's namespace assignments. When `wait_for_completion` is `false` (the default), the API responds immediately with a file ID and a `processing` status. ```json theme={null} { "status": "success", "file_id": "file_abc123", "file_name": "video.mp4", "file_status": "processing", "message": "File uploaded successfully and is being processed", "data": { "file_info": { "id": "file_abc123", "name": "video.mp4", "size": 15728640, "type": "video/mp4", "status": "processing", "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z", "has_transcript": false, "namespace_ids": ["64a1b2c3d4e5f6789abc0001"], "namespaces": [ { "id": "64a1b2c3d4e5f6789abc0001", "name": "default" } ] } } } ``` When `wait_for_completion` is `true`, the API waits until processing is finished before responding. ```json theme={null} { "status": "success", "file_id": "file_abc123", "file_name": "video.mp4", "file_status": "completed", "message": "File processed successfully", "data": { "file_info": { "id": "file_abc123", "name": "video.mp4", "size": 15728640, "type": "video/mp4", "duration": 330, "status": "completed", "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z", "has_transcript": true, "namespace_ids": ["64a1b2c3d4e5f6789abc0001", "64a1b2c3d4e5f6789abc0002"], "namespaces": [ { "id": "64a1b2c3d4e5f6789abc0001", "name": "default" }, { "id": "64a1b2c3d4e5f6789abc0002", "name": "Client Calls" } ] }, "transcript": [ { "text": "Welcome to this tutorial...", "start": 0.0, "end": 3.5 } ] } } ``` When `wait_for_completion` is `true` and processing exceeds 15 minutes, the API returns a `202 Accepted` response. Processing continues in the background — poll `GET /file/{file_id}` to check completion. ```json theme={null} { "status": "accepted", "file_id": "file_abc123", "file_name": "video.mp4", "file_status": "processing", "message": "File uploaded but processing is still in progress", "note": "Processing is taking longer than expected. Use GET /file/{file_id} to check status.", "data": { "file_info": { "id": "file_abc123", "name": "video.mp4", "size": 15728640, "type": "video/mp4", "status": "processing", "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z", "has_transcript": false, "namespace_ids": ["64a1b2c3d4e5f6789abc0001"], "namespaces": [ { "id": "64a1b2c3d4e5f6789abc0001", "name": "default" } ] } } } ``` # Get Usage Source: https://docs.vidnavigator.com/api-reference/endpoint/usage GET /usage Retrieve credit balances, per-service activity counts, storage, and channels indexed for the authenticated user. All API operations consume credits from a shared pool — there are no per-service limits. Retrieve your credit balances, resource usage counters, storage usage, and channel indexing metrics. ## Overview All API operations consume credits from a **shared credit pool** — there are no per-service limits. This endpoint gives you a complete picture of your account's consumption and remaining capacity. ### What's Included * **Credits**: Monthly allocation, remaining balance, and purchased credits * **Usage counters**: Activity counts per resource type (informational, not individual limits) * **Storage**: Used vs. allocated storage * **Channels indexed**: Non-public YouTube channels indexed * **Subscription details**: Plan, billing period, and status ## Example Request ```bash cURL theme={null} curl "https://api.vidnavigator.com/v1/usage" \ -H "X-API-Key: YOUR_API_KEY" ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/usage" headers = {"X-API-Key": "YOUR_API_KEY"} response = requests.get(url, headers=headers) usage = response.json() credits = usage["data"]["credits"] print(f"Monthly remaining: {credits['monthly_remaining']}") print(f"Purchased credits: {credits['purchased']}") ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/usage', { headers: { 'X-API-Key': 'YOUR_API_KEY' } }); const usage = await response.json(); const { credits } = usage.data; console.log(`Monthly remaining: ${credits.monthly_remaining}`); console.log(`Purchased credits: ${credits.purchased}`); ``` ## Success Response (200 OK) ```json theme={null} { "status": "success", "data": { "usage_period": { "start": "2026-03-01T00:00:00Z", "end": "2026-04-01T00:00:00Z" }, "billing_period": { "start": "2026-03-15T00:00:00Z", "end": "2026-04-15T00:00:00Z", "interval": "month" }, "subscription": { "plan_id": "pro", "plan_name": "Voyager", "interval": "month", "status": "active", "cancel_at_period_end": false }, "credits": { "monthly_total": 100, "monthly_remaining": 82.5, "purchased": 50 }, "usage": { "standard_request": { "used": 150 }, "residential_request": { "used": 45 }, "search_request": { "used": 30 }, "analysis_request": { "used": 12 }, "scene_analysis_hour": { "used": 3, "unit": "hours" }, "transcription_hour": { "used": 2.5, "unit": "hours" } }, "channels_indexed": { "used": 5, "limit": 15, "remaining": 10, "percentage": 33.3 }, "storage": { "used_bytes": 2684354560, "used_formatted": "2.5 GB", "limit_bytes": 536870912000, "limit_formatted": "500 GB", "remaining_bytes": 534186557440, "remaining_formatted": "497.5 GB", "percentage": 0.5 }, "generated_at": "2026-03-28T10:00:00Z" } } ``` ### Credits Object | Field | Type | Description | | - | - | - | | `monthly_total` | number \| `"unlimited"` | Total monthly credits included in your plan | | `monthly_remaining` | number \| `"unlimited"` | Monthly credits remaining for the current period | | `purchased` | number | Additional purchased credits (persist across billing periods) | Enterprise plans return `"unlimited"` for `monthly_total` and `monthly_remaining`. The shared credit pool is what limits usage — individual service counters under `usage` are informational only and do not impose per-service caps. ### Usage Counters The `usage` object shows how many resource units were used during the current period. These are **activity counters** — the shared credit pool is what actually limits usage. | Counter | Description | | - | - | | `standard_request` | Non-YouTube transcripts and non-proxy scraper page fetches | | `residential_request` | YouTube transcript retrievals and scraping requests that require residential proxies | | `search_request` | Semantic (vector) searches over already indexed content, including indexed YouTube channels and uploaded files (`/search/file`). Global YouTube and TikTok searches do **not** use this meter — they bill `residential_request` (and `analysis_request` for YouTube search). | | `analysis_request` | Video/file analysis and AI extraction units | | `scene_analysis_hour` | Scene analysis hours processed | | `transcription_hour` | Video/audio transcription hours processed by speech-to-text | ### Channels Indexed A hard plan limit (not credit-gated). Shows how many non-public YouTube channels you have indexed vs. your plan's capacity. This endpoint reports **cumulative** usage for the billing period. To see what a **single** API call charged, pass `include_usage=true` on that call and read the returned `usage` block — see [Per-Call Usage Disclosure](/api-reference/usage-costs#per-call-usage-disclosure). ### Error: No Subscription If you don't have an active subscription, the endpoint returns `404`: ```json theme={null} { "status": "error", "error": "no_subscription", "message": "You are not subscribed to any plan." } ``` # Search YouTube Source: https://docs.vidnavigator.com/api-reference/endpoint/youtube-search POST /youtube/search Search YouTube for videos using AI analysis and ranking with optional filters. **Process:** 1. Enhanced search with prompt engineering to find relevant videos 2. AI analysis of video content and transcripts (per-video residential proxy fetch + per-video LLM analysis) 3. Intelligent ranking based on query relevance 4. Structured results with rich metadata **Billing:** for each candidate video the API bills one `residential_request` when fetching the transcript — set `max_results` to cap how many candidates are processed and therefore how many residential charges fire. After all per-video AI analyses AND the ranking step complete, a single consolidated `analysis_request` charge is recorded with `quantity = ceil(total_tokens / 15000)`, where `total_tokens` is the sum of LLM input + output tokens across all videos analyzed plus the ranking call. No `search_request` is billed — the residential fetches + analysis fully cover the workflow. If the search returns zero results, nothing is billed. Set `include_usage=true` to receive the full per-charge breakdown — the consolidated `analysis_request` entry also carries a nested `tokens` object reporting `prompt_tokens` / `completion_tokens` / `total_tokens`. **Partial results:** if the user runs out of `residential_request` credits mid-search, the endpoint returns HTTP `402` with `status: "partial"`, the videos analyzed so far in `data.results`, and `error_code: "insufficient_credits_video_search"`. The `usage` block (when `include_usage=true`) reflects only the charges that succeeded. Search YouTube for videos using AI-powered analysis and ranking with optional filters. This endpoint was previously `/search/video`. It is now `/youtube/search` and is dedicated to searching YouTube. To search TikTok, use the [TikTok Search](/api-reference/endpoint/tiktok-search) endpoint. To search your own uploaded files, use [Search Files](/api-reference/endpoint/search-file). ## Overview Find the most relevant YouTube videos using natural language queries. Our AI analyzes video content, transcripts, and metadata to deliver precisely ranked results. ### Search Process 1. **Enhanced query processing**: AI expands and optimizes your search query 2. **Content analysis**: per-video transcript fetch (residential proxy) + per-video AI analysis 3. **Intelligent ranking**: results ranked by relevance, not just keyword matching 4. **Rich results**: detailed video information with explanations ## Billing Unlike a flat `search_request`, YouTube search bills for the work it actually does: * **One `residential_request` per candidate video** whose transcript is fetched. Use `max_results` to cap how many candidates are processed — that directly caps how many residential charges fire. * **One consolidated `analysis_request` charge** after all per-video analyses **and** the ranking step complete, with `quantity = ceil(total_tokens / 15000)` where `total_tokens` is the sum of LLM input + output tokens across every video analyzed plus the ranking call. * **No `search_request`** is billed — the residential fetches + analysis fully cover the workflow. * If the search returns zero results, **nothing is billed**. Set `include_usage: true` to receive the full per-charge breakdown. The consolidated `analysis_request` entry also carries a nested `tokens` object reporting `prompt_tokens` / `completion_tokens` / `total_tokens`. **Partial results:** if you run out of `residential_request` credits mid-search, the endpoint returns HTTP `402` with `status: "partial"`, the videos analyzed so far in `data.results`, and `error_code: "insufficient_credits_video_search"`. The `usage` block (when `include_usage=true`) reflects only the charges that succeeded. ### Search Options AI-powered query expansion and optimization (recommended) Filter results by publication date range Limit results by maximum video duration Prioritize relevance, popularity, or brevity ## Request Parameters | Parameter | Type | Required | Description | | - | - | - | - | | `query` | string | Yes | Search query | | `use_enhanced_search` | boolean | No | Whether to use enhanced search (default `true`) | | `start_year` | integer | No | Filter by start year | | `end_year` | integer | No | Filter by end year | | `focus` | string | No | `relevance` (default), `popularity`, or `brevity` | | `duration` | integer | No | Maximum duration in seconds | | `max_results` | integer | No | Maximum number of videos to analyse and return. Each candidate triggers one `residential_request` and contributes tokens to the consolidated `analysis_request` charge, so lowering this caps the per-call cost. Defaults to your plan ceiling; values above it are silently clamped. | | `include_usage` | boolean | No | When `true`, the response includes a `usage` block listing every meter charged, the total credits deducted, and the remaining balance. | ## Example Usage ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/youtube/search" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What are the best practices for React development?", "use_enhanced_search": true, "start_year": 2020, "end_year": 2024, "focus": "relevance", "duration": 1800, "max_results": 5 }' ``` ```python Python theme={null} import requests url = "https://api.vidnavigator.com/v1/youtube/search" headers = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json" } data = { "query": "What are the best practices for React development?", "use_enhanced_search": True, "start_year": 2020, "end_year": 2024, "focus": "relevance", "duration": 1800, "max_results": 5 } response = requests.post(url, headers=headers, json=data) results = response.json() for video in results['data']['results']: print(f"Title: {video['title']}") print(f"Relevance Score: {video['relevance_score']}") ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/youtube/search', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ query: 'What are the best practices for React development?', use_enhanced_search: true, start_year: 2020, end_year: 2024, focus: 'relevance', duration: 1800, max_results: 5 }) }); const results = await response.json(); ``` ## Response Example ```json theme={null} { "status": "success", "data": { "results": [ { "title": "React Best Practices 2024: Clean Code & Performance", "description": "Complete guide to writing maintainable React code...", "url": "https://youtube.com/watch?v=example123", "thumbnail": "https://img.youtube.com/vi/example123/maxresdefault.jpg", "channel": "React Mastery", "duration": "15:30", "views": 125000, "published_date": "2024-01-15", "relevance_score": 0.95, "explanation": "This video directly addresses React best practices with focus on clean code and performance optimization.", "key_topics": ["React hooks", "Performance optimization", "Code organization"] } ], "query": "What are the best practices for React development?", "total_found": 1, "explanation": "Found videos discussing React development best practices, focusing on recent content and high relevance." }, "usage": { "charges": [ { "service_type": "residential_request", "quantity": 5, "credits": 0.025 }, { "service_type": "analysis_request", "quantity": 2, "credits": 0.02, "tokens": { "prompt_tokens": 24000, "completion_tokens": 4200, "total_tokens": 28200 } } ], "total_credits": 0.045 } } ``` The `usage` block is only included when `include_usage=true` in the request. ## Partial Results (402) ```json theme={null} { "status": "partial", "error_code": "insufficient_credits_video_search", "message": "Ran out of credits after analyzing some videos.", "data": { "results": [ ... ], "query": "...", "total_found": 3, "explanation": "..." }, "usage": { "charges": [ ... ], "total_credits": 0.02 } } ``` ## Use Cases Find educational content on specific topics or technologies Build curated lists of videos for courses or documentation Analyze what content exists in your industry or niche Identify trending topics and popular content creators # Errors Source: https://docs.vidnavigator.com/api-reference/errors Error format and the error codes returned by the VidNavigator API ## Error format The API uses standard HTTP status codes and returns errors as JSON: ```json theme={null} { "status": "error", "error": "video_too_long", "message": "This video is longer than 10 minutes. Use POST /v1/transcribe/async instead.", "docs_url": "https://docs.vidnavigator.com/guides/async-jobs" } ``` | Field | Description | | - | - | | `status` | Always `error`. | | `error` | Machine-readable code. Branch on this, not on `message`. | | `message` | Human-readable explanation. | | `docs_url` | Present on actionable errors: a link to the page that explains how to fix it. | For [async jobs](/guides/async-jobs), a job that fails in the background is reported by the poll endpoint (`200`, `task_status: "failed"`) with a `data.error` object carrying the same `error` code plus the `http_status` the synchronous endpoint would have returned. ## Error codes ### 400 — Bad request | `error` | Meaning | | - | - | | `request_body_required` | The request body is missing. | | `missing_parameter` | A required parameter is missing. | | `invalid_parameter` | A parameter has an invalid value. | | `invalid_url` | The URL is malformed. | | `unsupported_url` / `unsupported_platform` | The URL or platform is not supported by this endpoint. | | `no_videos_found` | The post carries no video (e.g. an image-only Instagram carousel). | | `video_too_long` | The media is longer than **10 minutes**, the limit for synchronous endpoints. Use the [async endpoint](/guides/async-jobs). The metadata fetch that read the duration is billed and not refunded; no `transcription_hour` is charged. | | `invalid_schema` | The extraction schema is invalid. | | `input_too_large` | The input exceeds the size the endpoint accepts. | | `invalid_cursor` | The pagination cursor is invalid. | ### 401 — Unauthorized Missing, invalid or deactivated API key. ### 402 — Payment required | `error` | Meaning | | - | - | | `limit_exceeded` | Not enough credits, or no active subscription. Async submit endpoints return it when less than 60 seconds of transcription credit is left. | ### 403 — Forbidden | `error` | Meaning | | - | - | | `access_denied` | The API key lacks permission for this resource or endpoint. | | `content_restricted` | The content requires login or age verification. | ### 404 — Not found | `error` | Meaning | | - | - | | `video_not_found` | The video does not exist. | | `video_unavailable` | The video exists but cannot be accessed (private, removed, …). | | `transcript_not_available` | No transcript could be retrieved or generated. | | `file_not_found` | The uploaded file does not exist. | | `task_not_found` | The async task does not exist, has expired (results are kept 1 hour after the job finishes), or belongs to another account. | ### 413 — Payload too large | `error` | Meaning | | - | - | | `storage_quota_exceeded` | Your storage quota is full. | ### 429 — Too many requests | `error` | Meaning | | - | - | | `rate_limit_exceeded` | Per-endpoint rate limit exceeded. See [Rate Limiting](/api-reference/rate-limiting). | | `too_many_active_jobs` | Too many async jobs already running for this account. Wait for some to finish, then retry. | ### 451 — Unavailable for legal reasons | `error` | Meaning | | - | - | | `geo_restricted` | The content is not available in the region it is fetched from. | ### 500 — Server error | `error` | Meaning | | - | - | | `metadata_fetch_failed` | The video's metadata could not be fetched from the platform. | | `audio_extraction_failed` | The audio could not be extracted from the video. | | `transcription_failed` | Speech-to-text failed. | | `extraction_failed` | Structured data extraction failed. | | `internal_server_error` | Unexpected server error. | Charges are reverted when a request fails with a server error. ### 502 / 503 | Status | `error` | Meaning | | - | - | - | | `502` | — | Upstream fetch failed (e.g. the tweet could not be retrieved from X). | | `503` | `system_overload` | The system is temporarily overloaded. Retry later. | | `503` | `storage_not_configured` | File storage is not available. | # API Reference Source: https://docs.vidnavigator.com/api-reference/introduction Complete reference for the VidNavigator Developer API ## Welcome to VidNavigator API The VidNavigator Developer API provides transcription, analysis, and structured data extraction across YouTube, TikTok, Instagram, X/Twitter, Facebook, Vimeo, Loom, Dailymotion, and uploaded files. It also supports global YouTube and TikTok discovery plus semantic search over indexed YouTube channel content and uploaded files. View the complete OpenAPI 3.0 specification Quick start guide for making your first API call ## Authentication All API endpoints require authentication using an API key passed in the `X-API-Key` header: ```bash theme={null} X-API-Key: YOUR_API_KEY ``` Get your API key from the [VidNavigator Developer Dashboard](https://vidnavigator.com/studio/api). ## Base URL The API is available at: ``` https://api.vidnavigator.com/v1 ``` ## Long Videos and Async Jobs The speech-to-text endpoints run for as long as the media is long, so each one has an **async twin**. Synchronous calls are limited to **10 minutes** of media and reject longer media with `400 video_too_long`; the async endpoints have no duration cap and work for short videos too. | Synchronous (≤ 10 min) | Async (any length) | | - | - | | [`POST /transcribe`](/api-reference/endpoint/transcribe) | `POST /transcribe/async` → `GET /transcribe/{task_id}` | | [`POST /extract/video`](/api-reference/endpoint/extract-video) | `POST /extract/video/async` → `GET /extract/video/{task_id}` | | [`POST /tweet/statement`](/api-reference/endpoint/tweet-claim-analysis) | `POST /tweet/statement/async` → `GET /tweet/statement/{task_id}` | The async endpoint returns `202` with a `task_id` immediately. Poll the `check_status_url` (free), or get called back through a [webhook](/webhooks) — set a default endpoint in [Studio → API](https://vidnavigator.com/studio/api) or pass `webhook_url` per request. Submit, poll and migrate from synchronous calls Payload, signature verification and retries ## Error Handling The API uses standard HTTP status codes and returns structured error responses: ```json theme={null} { "status": "error", "error": "error_type", "message": "Human readable error message", "docs_url": "https://docs.vidnavigator.com/..." } ``` Actionable errors also carry a `docs_url` pointing to the page that explains how to fix them. See [Errors](/api-reference/errors) for every error code. ### Common Error Codes | Status Code | Error Type | Description | | - | - | - | | 400 | `missing_parameter`, `invalid_parameter`, … | Invalid request parameters | | 400 | `video_too_long` | Media longer than 10 minutes sent to a synchronous endpoint — use the async endpoint | | 401 | — | Missing or invalid API key | | 402 | `limit_exceeded` | Credit limit exceeded or no active subscription | | 403 | `access_denied` | Insufficient permissions | | 404 | `video_not_found`, `task_not_found`, … | Resource not found | | 429 | `rate_limit_exceeded`, `too_many_active_jobs` | Rate limit exceeded, or too many async jobs running | | 500 | `internal_server_error` | Server error | ## API Categories Extract transcripts from YouTube, TikTok, Instagram, X/Twitter, Facebook, Vimeo, Loom, and more Upload, manage, and organize audio/video files with namespaces Analyze video content with AI for insights and summaries Extract structured data from transcripts using custom schemas Discover videos with global YouTube and TikTok search Search indexed YouTube channel content and uploaded files using vector similarity # Rate Limiting Source: https://docs.vidnavigator.com/api-reference/rate-limiting Per-endpoint request limits and throttling behaviour VidNavigator applies per-endpoint rate limits to ensure fair usage and platform stability. ## Current Limits | Endpoint | Requests / minute | | - | -: | | `GET /health` | **300** | | `GET /usage` | **300** | | `POST /transcript` | **200** | | `GET /files` | **200** | | `GET /file/{file_id}` | **200** | | `POST /analyze/video` | **30** | | `POST /analyze/file` | **30** | | `POST /transcribe` | **20** | | `POST /youtube/search` | **10** | | `POST /search/file` | **10** | | `POST /upload/file` | **3** | > **Temporary Ban**\ > Exceeding a limit results in a **429 Too Many Requests** response and a temporary ban of **1 minute** on the offending API key. ### Async Jobs The [async submit endpoints](/guides/async-jobs) (`POST /transcribe/async`, `POST /extract/video/async`, `POST /tweet/statement/async`) also limit how many jobs can run at the same time for one account. Above that limit, the submit returns **429** with `error: "too_many_active_jobs"` — wait for running jobs to finish, then retry. Polling the result endpoints is free. ### Enterprise Plans Enterprise customers can request higher or custom rate limits. Contact us at [contact@vidnavigator.com](mailto:contact@vidnavigator.com) for details. # Usage Costs Source: https://docs.vidnavigator.com/api-reference/usage-costs Understand how API operations consume credits from your shared credit pool. ## Overview Your VidNavigator plan includes a monthly **credit pool** shared across all API operations. Different services consume credits at different rates. You can monitor your current balance at any time by calling the [`/usage`](/api-reference/endpoint/usage) endpoint. There are **no per-service limits** — all operations draw from the same credit pool. The activity counters shown in `/usage` are informational only. ## Credit Costs per Service | Resource Unit | Credit Conversion | Used By | | - | - | - | | Standard Requests (`standard_request`) | 1 credit = 10,000 non-YouTube transcripts or non-proxy scraper pages | `/transcript` (non-YouTube), non-proxy TikTok profile pages | | Residential Proxy Requests (`residential_request`) | 1 credit = 200 residential fetches | `/transcript` (YouTube), `/youtube/search`, `/tiktok/search`, proxy-backed TikTok profile pages | | Analysis Requests (`analysis_request`) | 1 credit = 100 analysis requests (token-based, see below) | `/analyze/video`, `/analyze/file`, `/extract/video`, `/extract/file`, `/youtube/search`, `/search/file`, `/tweet/statement` | | Search Requests (`search_request`) | 1 credit = 1,000 semantic searches | Indexed YouTube channel search, `/search/file` | | Transcription Hours (`transcription_hour`) | 1 credit = 3 hours of video/audio transcription | `/upload/file`, `/transcribe` (and `/transcribe/async`), speech-to-text fallback | | Scene Analysis Hours (`scene_analysis_hour`) | 1 credit = 1 hour of scene analysis | Scene analysis endpoints | ### Deduction Order Credits are deducted in this order: 1. **Monthly credits** are consumed first 2. **Purchased credits** cover any overflow Monthly credits reset each billing period. Purchased credits persist across periods. ## Plan Credit Allowances | Plan | Monthly Credits | Storage | Indexed Channels | | - | - | - | - | | Trial | 5 | 5 GB | 2 | | Navigator (\$19.99/mo) | 20 | 50 GB | 3 | | Voyager (\$79.99/mo) | 100 | 500 GB | 15 | | Odyssey (Custom) | Unlimited | Unlimited | Unlimited | Voyager and above can purchase additional credit packs: 50 credits ($20), 300 credits ($100), or 1,200 credits (\$300). ## Service Details Each non-YouTube transcript request to `/transcript` consumes `standard_request` units. 1 credit covers 10,000 standard requests. Fetches that route through residential proxies consume `residential_request` units. 1 credit covers 200 residential requests. For YouTube, even `metadata_only: true` routes through the residential proxy and is billed as a `residential_request`. YouTube search and TikTok search also bill per residential page fetched. `/transcribe`, `/upload/file`, and speech-to-text fallback consume `transcription_hour` units. 1 credit covers 3 hours of audio/video processed. Files uploaded via `/upload/file` also count towards your storage quota. `/analyze/*`, `/extract/*`, `/youtube/search`, `/search/file`, and `/tweet/statement` consume `analysis_request` units. 1 credit covers 100 analysis requests. LLM analysis is **token-based**: 1 `analysis_request` unit covers up to 15,000 total tokens. Larger contexts use `ceil(total_tokens / 15000)` units. Multiple internal LLM calls in one request are consolidated into a single charge. Semantic (vector) searches over already indexed content consume a `search_request` unit. This includes indexed YouTube channel search and each call to `/search/file`. 1 credit covers 1,000 semantic searches. Global YouTube search (`/youtube/search`) and TikTok keyword search (`/tiktok/search`) do **not** bill `search_request` — they bill per `residential_request` (and `analysis_request` for YouTube search) instead. Each call to `/extract/video` or `/extract/file` uses AI to extract structured data from transcripts. Analysis Request units scale with token usage. ## Per-Call Usage Disclosure Most billable endpoints accept an `include_usage` flag. When set to `true`, the response includes a `usage` block that lists **exactly what was charged for that specific call** — every meter that fired, the net credits deducted, and (for LLM endpoints) the token tally. ```json theme={null} { "usage": { "charges": [ { "service_type": "residential_request", "quantity": 5, "credits": 0.025 }, { "service_type": "analysis_request", "quantity": 2, "credits": 0.02, "tokens": { "prompt_tokens": 24000, "completion_tokens": 4200, "total_tokens": 28200 } } ], "total_credits": 0.045 } } ``` | Field | Description | | - | - | | `charges[]` | One entry per meter (`service_type`). Multiple billings of the same meter in one request are consolidated. | | `charges[].quantity` | Net units consumed. For `analysis_request` this is `ceil(total_tokens / 15000)`; for `transcription_hour` / `scene_analysis_hour` it's in hours. | | `charges[].credits` | Net credits deducted for this meter (`0` when waived). | | `charges[].waived` / `credits_saved` | Present when a charge was waived via a cache-hit sponsorship. | | `charges[].tokens` | Only on the `analysis_request` entry for LLM endpoints — cumulative `prompt_tokens` / `completion_tokens` / `total_tokens`. | | `total_credits` | Net credits deducted by the whole request. | For **async** endpoints (`/transcribe/async`, `/extract/video/async`, `/tweet/statement/async`, `/tiktok/profile`, `/tiktok/search`), billing happens in the background worker, so `include_usage` is passed on the polling `GET` (once `task_status=completed`), not on submit. See [Async Jobs](/guides/async-jobs). ## Async Jobs and Long Videos * Async endpoints are billed **exactly like** their synchronous counterparts — the async mode costs nothing extra. * Polling result endpoints is **free**. * A failed async job has all of its charges reverted. * Async submit endpoints return `402 limit_exceeded` when less than **60 seconds** of transcription credit is left. The real, duration-based charge is computed when the job runs. * A synchronous request rejected with `video_too_long` (media longer than 10 minutes) still bills the metadata fetch that read the duration (`standard_request` or `residential_request`, **not refunded**). No `transcription_hour` is charged, since no audio is processed. This per-call `usage` block is distinct from the aggregate [`GET /usage`](/api-reference/endpoint/usage) endpoint, which reports cumulative counters and balances for the whole billing period. ## Hard Limits (Not Credit-Gated) Some resources have fixed plan limits that are **not** deducted from your credit pool: | Resource | How It's Enforced | | - | - | | **Storage** | Total file storage in bytes checked against your plan's storage cap | | **Indexed Channels** | Non-public YouTube channels counted live from your channel list against your plan's cap | ## YouTube vs Other Platforms **Why do Residential Proxy Requests cost more?** YouTube and some scraper flows require residential proxy infrastructure for reliable access. This is reflected in the credit rate (1 credit per 200 Residential Proxy Requests vs. 1 per 10,000 Standard Requests). ## Free Endpoints The following endpoints do **not** consume credits: * `GET /files` — List your uploaded files * `GET /file/{file_id}` — Get file info and transcript * `GET /file/{file_id}/url` — Get temporary file URL * `GET /namespaces` — List namespaces * `POST /namespaces` — Create a namespace * `PUT /namespaces/{namespace_id}` — Rename a namespace * `DELETE /namespaces/{namespace_id}` — Delete a namespace * `PUT /file/{file_id}/namespaces` — Update file namespace assignments * `GET /tiktok/profile/{task_id}` — Poll a TikTok profile scrape (billing happens in the background worker) * `GET /tiktok/search/{task_id}` — Poll a TikTok keyword search (billing happens in the background worker) * `GET /health` — Health check * `GET /usage` — Usage statistics # Chrome Extension Source: https://docs.vidnavigator.com/chrome-extension Find exact moments in YouTube videos with the VidNavigator Chrome Extension ## VidNavigator Chrome Extension The VidNavigator Chrome Extension brings powerful video search and analysis capabilities directly to your browser, allowing you to find exact moments in YouTube videos without leaving the page. Get VidNavigator from the Chrome Web Store ## Features Search for specific moments within any YouTube video using natural language queries. Get precise timestamps for the moments you're looking for with AI-powered accuracy. Find relevant content based on meaning, not just keywords. Jump directly to the exact moment in the video with a single click. ## How to Use Install the VidNavigator Chrome Extension from the Chrome Web Store. Open any YouTube video you want to search through. Click the VidNavigator extension icon in your browser toolbar. Enter your search query in natural language: * "Where does he talk about machine learning?" * "Show me the part about pricing" * "Find the demo section" Click on any search result to jump directly to that moment in the video. ## Example Searches Here are some example queries you can try with the extension: * "Explain the concept of neural networks" * "Show me the example calculation" * "Where is the homework assignment mentioned?" * "Find the part about the final exam" * "How to install the software" * "Show me the troubleshooting section" * "Where does the configuration start?" * "Find the advanced settings" * "When do they discuss career advice?" * "Find the part about startup funding" * "Show me where they talk about the new product" * "Where is the Q\&A session?" * "Show me the performance benchmarks" * "Find the pricing information" * "Where do they compare it to competitors?" * "Show me the unboxing part" ## Key Benefits ### 🎯 **Save Time** No more scrubbing through long videos. Find exactly what you're looking for in seconds. ### 🧠 **Smart Understanding** Our AI understands context and meaning, not just exact word matches. ### 📚 **Perfect for Learning** Great for students, researchers, and professionals who need to find specific information in educational content. ### 🔍 **Works on Any Video** Compatible with all YouTube videos that have captions or transcripts available. ## Technical Requirements * **Browser**: Chrome (version 88+) * **Platform**: Works on any website with YouTube videos * **Internet**: Requires internet connection for AI processing * **Account**: Free VidNavigator account (sign up at [vidnavigator.com](https://vidnavigator.com)) ## Privacy & Security * Your search queries are processed securely through our API * No personal browsing data is stored * Video content is not downloaded or stored locally * All processing happens in the cloud with enterprise-grade security The extension only requests minimal permissions: * Access to YouTube pages (to integrate with video player) * Network access (to communicate with VidNavigator API) * No access to other websites or personal data ## Troubleshooting 1. Make sure you're on a YouTube video page 2. Check that the video has captions/transcripts available 3. Verify your internet connection 4. Try refreshing the page and reopening the extension * Try rephrasing your search query * Make sure the topic you're searching for is actually discussed in the video * Check if the video has accurate captions (auto-generated captions may be less reliable) * Be more specific in your search terms * Try using different keywords or phrases * Consider the context and how the speaker might phrase the topic ## Usage Limits The Chrome extension uses your VidNavigator account's API quota: * **Free Account**: Limited searches per month * **Paid Plans**: Higher limits based on your subscription tier *** Enhance your YouTube viewing experience with intelligent video search. Install the VidNavigator Chrome Extension today! # Contact Us Source: https://docs.vidnavigator.com/contact Reach out to the VidNavigator team for support, feature requests, or general questions. Have a feature request, need help with your integration, or just have a general question? We would love to hear from you! Reach out to us at **[support@vidnavigator.com](mailto:support@vidnavigator.com)** or **[contact@vidnavigator.com](mailto:contact@vidnavigator.com)**. We read all our emails and promise to reply to you quickly. # Analyzing Videos Source: https://docs.vidnavigator.com/guides/analyzing-videos Learn how to analyze videos to extract summaries, topics, and other insights. ## Overview VidNavigator's analysis capabilities allow you to go beyond simple transcription and understand the content of a video at a deeper level. You can get an AI-generated summary, identify key topics and speakers, and even ask specific questions about the video's content. Every analysis returns a `transcript_analysis` object with: * `summary`: an overview of the content * `people`: people mentioned, with the context they appear in * `places`: locations referenced * `key_subjects`: important topics, with a description and importance * `query_answer`: when you pass a `query`, the direct `answer` plus the `relevant_segments` of the transcript that support it ## Prerequisites * A valid VidNavigator API key. * The [Python](/sdks/python) or [JavaScript](/sdks/javascript) SDK, version 2.1.0 (`pip install vidnavigator==2.1.0` or `npm install vidnavigator@2.1.0`). ## Billing AI analysis consumes `analysis_request` units based on context size. One unit covers up to **15,000 total tokens**, and longer contexts are billed with `ceil(total_tokens / 15000)`. For example, a video that takes 17,000 total tokens costs 2 `analysis_request` units. Pass `include_usage=True` (Python) or `include_usage: true` (JavaScript) to receive a `usage` block with the exact charges and LLM token counts. ## Analyzing a Video ```python Python theme={null} from vidnavigator import VidNavigatorClient, VidNavigatorError client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY try: resp = client.analyze_video( video_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", query="What is the main theme of this song?", ) analysis = resp.data.transcript_analysis print("Summary:", analysis.summary) print("Answer to your query:", analysis.query_answer.answer) # people, places and key_subjects are lists of plain dicts for person in analysis.people or []: print("Person:", person) # {"name": ..., "context": ...} for subject in analysis.key_subjects or []: print("Subject:", subject) # {"name": ..., "description": ..., "importance": ...} except VidNavigatorError as e: print(f"An error occurred: {e}") ``` ```javascript JavaScript theme={null} import { VidNavigatorClient, VidNavigatorError } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY, }); try { const { transcript_analysis } = await vn.analyzeVideo({ video_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', query: 'What is the main theme of this song?', }); console.log('Summary:', transcript_analysis.summary); console.log('Answer to your query:', transcript_analysis.query_answer?.answer); console.log('Supporting excerpts:', transcript_analysis.query_answer?.relevant_segments); for (const person of transcript_analysis.people ?? []) { console.log(`Person: ${person.name} -- ${person.context}`); } for (const subject of transcript_analysis.key_subjects ?? []) { console.log(`Subject: ${subject.name} -- ${subject.description}`); } } catch (error) { if (error instanceof VidNavigatorError) { console.error(`An error occurred: ${error.message}`); } else { throw error; } } ``` `query` is optional. Without it you still get the summary, people, places and key subjects. ## Analyzing an Uploaded File Files you have [uploaded](/guides/uploading-videos) are analyzed the same way, by `file_id`: ```python Python theme={null} resp = client.analyze_file( file_id="your_file_id", query="What are the key action items from this meeting?", ) print(resp.data.transcript_analysis.summary) print(resp.data.transcript_analysis.query_answer.answer) ``` ```javascript JavaScript theme={null} const { transcript_analysis } = await vn.analyzeFile({ file_id: 'your_file_id', query: 'What are the key action items from this meeting?', }); console.log(transcript_analysis.summary); console.log(transcript_analysis.query_answer?.answer); ``` ## Next Steps * Need specific fields rather than a free-form summary? Use [structured data extraction](/guides/data-extraction). * See the [Analyze Video](/api-reference/endpoint/analyze-video) and [Analyze File](/api-reference/endpoint/analyze-file) API references. # Async Jobs & Long Videos Source: https://docs.vidnavigator.com/guides/async-jobs Transcribe, extract and analyze videos of any length with async jobs, polling and webhooks. ## Overview The speech-to-text endpoints run for as long as the media is long. A synchronous request holds the HTTP connection open for the whole download and transcription — reliable for short clips, not for long ones: clients, reverse proxies and marketplace gateways all time out. That is why every speech-to-text endpoint has an **async twin**. You submit the job, get a `task_id` back immediately, and collect the result by polling or through a [webhook](/webhooks). **Synchronous calls are limited to 10 minutes of media.** Longer media is rejected with `400 video_too_long`. Use the async endpoints for anything that may run past 10 minutes — they have **no duration cap**, and they work just as well for short videos. ## Which endpoint to use | Synchronous (≤ 10 min) | Async submit (any length) | Poll result | | - | - | - | | [`POST /transcribe`](/api-reference/endpoint/transcribe) | [`POST /transcribe/async`](/api-reference/endpoint/transcribe-async) | `GET /transcribe/{task_id}` | | [`POST /extract/video`](/api-reference/endpoint/extract-video) (with `transcribe=true`) | [`POST /extract/video/async`](/api-reference/endpoint/extract-video-async) | `GET /extract/video/{task_id}` | | [`POST /tweet/statement`](/api-reference/endpoint/tweet-claim-analysis) | [`POST /tweet/statement/async`](/api-reference/endpoint/tweet-claim-analysis-async) | `GET /tweet/statement/{task_id}` | Each async endpoint accepts **exactly the same body** as its synchronous counterpart, plus an optional `webhook_url`. The result you get back is **identical** to the synchronous response's `data` block, so the same parsing code works for both. If you don't know the video's duration in advance, just use the async endpoint every time. There is no extra cost. [TikTok profile scrape](/api-reference/endpoint/tiktok-profile) and [TikTok keyword search](/api-reference/endpoint/tiktok-search) are async by design and follow the same polling and webhook model. ## Using the SDKs The [Python](/sdks/python#background-jobs) and [JavaScript](/sdks/javascript#background-jobs) SDKs (v2.1.0) **always use the async endpoints**, so you don't need to write any polling code. A single call submits the job, polls until it finishes, and returns the result (or raises the same error the synchronous endpoint would have returned): ```python Python theme={null} from vidnavigator import VidNavigatorClient client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY # Wait for the result, however long the video is result = client.transcribe_video(video_url="https://www.tiktok.com/@user/video/1234567890") print(result.data.video_info.title) # Or submit now and collect later job = client.submit_transcribe_video( video_url="https://www.tiktok.com/@user/video/1234567890", webhook_url="https://example.com/hooks/vidnavigator", # optional ) print(job.task_id, job.status()) # "processing" result = job.result() # waits, then returns the same object as transcribe_video # From another process, within 1 hour of the job finishing result = client.resume_job("transcribe", job.task_id).result() ``` ```python Python (asyncio) theme={null} import asyncio from vidnavigator import AsyncVidNavigatorClient # pip install "vidnavigator[async]" async def main(): async with AsyncVidNavigatorClient() as client: # reads VIDNAVIGATOR_API_KEY # Wait for the result without blocking the event loop result = await client.transcribe_video(video_url="https://www.tiktok.com/@user/video/1234567890") print(result.data.video_info.title) # Or submit now and collect later job = await client.submit_transcribe_video( video_url="https://www.tiktok.com/@user/video/1234567890", webhook_url="https://example.com/hooks/vidnavigator", # optional ) print(job.task_id, await job.status()) # "processing" result = await job.result() # Later, within 1 hour of the job finishing (resume_job itself is not awaited) result = await client.resume_job("transcribe", job.task_id).result() asyncio.run(main()) ``` ```javascript JavaScript theme={null} import { VidNavigatorClient } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY }); // Wait for the result, however long the video is const { video_info, transcript } = await vn.transcribe('https://www.tiktok.com/@user/video/1234567890'); console.log(video_info.title); // Or submit now and collect later const job = await vn.transcribe.submit({ video_url: 'https://www.tiktok.com/@user/video/1234567890', webhook_url: 'https://example.com/hooks/vidnavigator', // optional }); console.log(job.task_id, await job.status()); // 'processing' const result = await job.result(); // waits, then returns the same shape as vn.transcribe() // From another process, within 1 hour of the job finishing const again = await vn.transcribe.resume(job.task_id).result(); ``` The same pattern covers every async operation. The Python `AsyncVidNavigatorClient` (new in 2.1.0) has the same method names as the sync client, as coroutines: | Operation | Python (blocking / non-blocking) | JavaScript (wait / start) | | - | - | - | | Transcription | `transcribe_video` / `submit_transcribe_video` | `vn.transcribe()` / `vn.transcribe.submit()` | | Structured extraction | `extract_video_data` / `submit_extract_video_data` | `vn.extractVideo()` / `vn.extractVideo.submit()` | | Tweet claim analysis | `get_tweet_statement` / `submit_tweet_statement` | `vn.tweetStatement()` / `vn.tweetStatement.submit()` | | TikTok profile scrape | `scrape_tiktok_profile` / `submit_tiktok_profile_scrape` | `vn.tiktokProfile()` / `vn.tiktokProfile.submit()` | | TikTok keyword search | `search_tiktok` / `submit_tiktok_search` | `vn.tiktokSearch()` / `vn.tiktokSearch.submit()` | The blocking calls wait up to 1 hour (Python) or 30 minutes (JavaScript) by default. On timeout they raise `JobTimeoutError` / `TaskTimeoutError` carrying the `task_id`. The job keeps running, so you can resume it. See the SDK pages for batch recipes and the running-job limit. The rest of this guide describes the underlying HTTP protocol, for when you call the API directly. ## How it works `POST` to the async endpoint. The API validates the request and returns `202 Accepted` with a `task_id` and a `check_status_url`. Either poll `check_status_url` every few seconds while `task_status` is `processing`, or pass a `webhook_url` (or configure a default one in [Studio → API](https://vidnavigator.com/studio/api)) and get called back when the job finishes. When `task_status` is `completed`, `data.result` holds the result. When it is `failed`, `data.error` carries the error code and HTTP status the synchronous endpoint would have returned. ## 1. Submit the job ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/transcribe/async" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.tiktok.com/@user/video/1234567890", "webhook_url": "https://example.com/hooks/vidnavigator" }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.vidnavigator.com/v1/transcribe/async", headers={"X-API-Key": "YOUR_API_KEY"}, json={"video_url": "https://www.tiktok.com/@user/video/1234567890"}, ) response.raise_for_status() task = response.json()["data"] print(task["task_id"], task["check_status_url"]) ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.vidnavigator.com/v1/transcribe/async', { method: 'POST', headers: { 'X-API-Key': 'YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ video_url: 'https://www.tiktok.com/@user/video/1234567890' }) }); const { data: task } = await response.json(); console.log(task.task_id, task.check_status_url); ``` ### Accepted response (202) ```json theme={null} { "status": "success", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "task_status": "processing", "job_type": "transcribe", "expires_at": "2026-09-23T13:00:00Z", "check_status_url": "/v1/transcribe/550e8400-e29b-41d4-a716-446655440000", "webhook_url": "https://example.com/hooks/vidnavigator", "message": "Transcription job accepted.", "docs_url": "https://docs.vidnavigator.com/guides/async-jobs" } } ``` | Field | Description | | - | - | | `task_id` | Identifier of the job. | | `task_status` | Always `processing` at submit time. | | `job_type` | `transcribe`, `extract_video` or `tweet_statement`. | | `expires_at` | When the result is deleted. Provisional until the job finishes, then pushed out to **finish time + 1 hour**. | | `check_status_url` | Relative URL to poll (prefix it with `https://api.vidnavigator.com`). | | `webhook_url` | The URL the result will be POSTed to, or `null` if no webhook applies. | ## 2. Poll for the result Polling is **free** — the `GET` endpoints consume no credits. A few seconds between polls is plenty. ```python Python theme={null} import time import requests BASE = "https://api.vidnavigator.com" HEADERS = {"X-API-Key": "YOUR_API_KEY"} def wait_for_job(check_status_url, interval=5): while True: job = requests.get(BASE + check_status_url, headers=HEADERS).json()["data"] if job["task_status"] == "completed": return job["result"] if job["task_status"] == "failed": err = job["error"] raise RuntimeError(f'{err["error"]} ({err["http_status"]}): {err["message"]}') time.sleep(interval) result = wait_for_job(task["check_status_url"]) print(result["video_info"]["title"]) for segment in result["transcript"]: print(f'[{segment["start"]:.2f}s] {segment["text"]}') ``` ```javascript JavaScript theme={null} const BASE = 'https://api.vidnavigator.com'; const HEADERS = { 'X-API-Key': 'YOUR_API_KEY' }; async function waitForJob(checkStatusUrl, intervalMs = 5000) { while (true) { const { data: job } = await (await fetch(BASE + checkStatusUrl, { headers: HEADERS })).json(); if (job.task_status === 'completed') return job.result; if (job.task_status === 'failed') { throw new Error(`${job.error.error} (${job.error.http_status}): ${job.error.message}`); } await new Promise(r => setTimeout(r, intervalMs)); } } const result = await waitForJob(task.check_status_url); console.log(result.video_info.title); ``` ### Job states | `task_status` | Meaning | | - | - | | `processing` | Accepted and being worked on. | | `completed` | Terminal. `data.result` holds the result. | | `failed` | Terminal. `data.error` holds `error`, `message` and `http_status`. All charges are reverted. | ### Completed response ```json theme={null} { "status": "success", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "task_status": "completed", "job_type": "transcribe", "request": { "video_url": "https://www.tiktok.com/@user/video/1234567890", "transcript_text": false, "all_videos": false }, "created_at": "2026-09-23T12:00:00Z", "started_at": "2026-09-23T12:00:01Z", "completed_at": "2026-09-23T12:03:40Z", "expires_at": "2026-09-23T13:03:40Z", "check_status_url": "/v1/transcribe/550e8400-e29b-41d4-a716-446655440000", "webhook": { "status": "delivered", "attempts": 1, "response_status": 200, "last_error": null, "delivered_at": "2026-09-23T12:03:41Z" }, "result": { "video_info": { "title": "A long podcast episode", "duration": 3540.0 }, "transcript": [ { "text": "Welcome to the show.", "start": 0.5, "end": 2.1 } ] }, "error": null } } ``` ### Failed response ```json theme={null} { "status": "success", "data": { "task_id": "550e8400-e29b-41d4-a716-446655440000", "task_status": "failed", "job_type": "transcribe", "result": null, "error": { "error": "transcript_not_available", "message": "Transcript could not be generated for this video.", "http_status": 404 } } } ``` The poll request itself returns `200` even when the job failed — check `task_status`. `data.error` uses the same codes as the synchronous endpoint, so existing error handling can be reused unchanged. See [Errors](/api-reference/errors). ### Retention * Results are kept for **1 hour after the job finishes** (not from submission), so even a long transcription leaves a full hour to collect it. * Reading a task does **not** consume or delete it — re-read the same `task_id` as often as you like until it expires. * After expiry, the poll endpoint returns `404 task_not_found`. ## Webhooks instead of polling Pass `webhook_url` on the submit request, or configure an account-level default endpoint in [Studio → API](https://vidnavigator.com/studio/api). The per-request value wins; pass `"webhook_url": ""` to opt a single job out of the default. See the full contract — payload, signature verification, retries — in the [Webhooks guide](/webhooks). Webhooks are best effort. Treat polling as the source of truth: if a callback never arrives, the result is still available on `check_status_url`. ## Billing Billing is **identical** to the synchronous endpoint — the async mode costs nothing extra — and happens in the background worker. * `include_usage` is **not** accepted on the submit endpoint (the `202` fires before any charge is made). Pass `include_usage=true` on the poll endpoint instead: it replays the final charges once `task_status=completed`. * A failed job has all of its charges reverted. * Polling is free. See [Usage & Costs](/api-reference/usage-costs) for unit prices. ## Submit-time errors These are returned by the `POST` itself; no task is created. | Status | `error` | When | | - | - | - | | `400` | `missing_parameter`, `invalid_parameter`, `invalid_schema`, … | Invalid body. For `/extract/video/async` the schema is validated at submit time, so an invalid schema is rejected before any transcription is billed. | | `401` | — | Invalid or deactivated API key. | | `402` | `limit_exceeded` | Less than **60 seconds** of transcription credit left. This is a spam gate, not a quote: the real charge is computed when the job runs, and a job whose video exceeds your remaining balance still fails with the same credit error a synchronous call would get. | | `403` | — | The API key lacks permission for this endpoint. | | `429` | `too_many_active_jobs` | Too many jobs already running for this account. Wait for some to finish, then retry. | ## Migrating from synchronous calls 1. Switch the URL: `/transcribe` → `/transcribe/async`, `/extract/video` → `/extract/video/async`, `/tweet/statement` → `/tweet/statement/async`. Keep the same body. 2. Remove `include_usage` from the submit body and pass it as a query parameter on the poll endpoint if you need it. 3. Poll `check_status_url` (or receive the webhook) and read `data.result` — it has the same shape as the synchronous `data` block. 4. Map `data.error.error` to your existing error handling. # Extracting Structured Data Source: https://docs.vidnavigator.com/guides/data-extraction Turn any online video or uploaded file into clean, validated JSON using a schema you define. If transcripts are for humans, extraction is for software. The Extract API lets you define the exact fields you want, then returns structured JSON that matches your schema so you can send the result straight into a database, CRM, workflow, or AI pipeline. ## What This Guide Helps You Do By the end of this guide, you will know how to: * choose the right extraction endpoint * design a schema the model can follow reliably * use `what_to_extract` to improve focus * understand auto-transcription, caching, and billing * move from one test video to a repeatable production workflow ## Why This Is More Than Just an LLM Prompt The real challenge is not only extracting structured data. It is getting the video content into a usable text layer in the first place. With VidNavigator, the workflow is end to end: * fetch the transcript when the platform already exposes one * auto-transcribe supported non-YouTube videos with speech-to-text when no transcript exists * run AI extraction on that text using your schema * return validated JSON you can use immediately in software That is the main difference. You are not expected to manually retrieve captions, run a separate speech-to-text step, clean the text, and then prompt a model yourself. Compared with building that flow manually, VidNavigator gives you: * transcript retrieval and speech-to-text built into the same extraction workflow * one API call instead of stitching together multiple tools * a fixed response shape defined by your schema * validated output instead of free-form text * prompt compilation that is cached for 2 hours for repeated schemas This is what makes video data extraction practical at scale, especially for platforms where transcripts are inconsistent, missing, or not easily accessible. ## Choose the Right Endpoint Use `/v1/extract/video` when you have a public video URL from YouTube, Instagram, TikTok, Facebook, X, Vimeo, Dailymotion, Loom, and similar platforms. Use `/v1/extract/file` when the content is already in your VidNavigator library and you want to extract from a `file_id`. `extract/video` can auto-transcribe non-YouTube videos when `transcribe=true` (the default). `extract/file` does not transcribe for you, so the file must already have a transcript. **Long videos:** when speech-to-text is needed, the synchronous `/v1/extract/video` accepts media up to **10 minutes** and rejects longer media with `400 video_too_long`. Use [`/v1/extract/video/async`](/api-reference/endpoint/extract-video-async) — same body, no duration cap, result by polling or [webhook](/webhooks). See [Async Jobs](/guides/async-jobs). ## How Extraction Works The Extract API runs a simple 2-step pipeline: 1. You send a schema and optional `what_to_extract` instruction. 2. VidNavigator compiles an optimized extraction plan and caches it for 2 hours. 3. The plan is applied to the transcript for the current video or file. 4. The response is validated so the output matches your schema. That cache matters. The first call for a new schema usually has a small compilation overhead. Reusing the same schema on later calls is faster because the compiled prompt is reused automatically. ## Start with a Small Schema The fastest way to succeed is to start with 2-4 fields, validate the output, then expand. Here is a good starter schema for product review videos: ```json JSON theme={null} { "products_mentioned": { "type": "Array", "description": "Products mentioned in the video", "items": { "type": "String", "description": "Product name" } }, "main_claim": { "type": "String", "description": "The single most important claim or conclusion made in the video" }, "sentiment": { "type": "Enum", "description": "Overall sentiment toward the main product discussed", "enum": ["positive", "neutral", "negative"] } } ``` ```yaml YAML theme={null} products_mentioned: type: Array description: Products mentioned in the video items: type: String description: Product name main_claim: type: String description: The single most important claim or conclusion made in the video sentiment: type: Enum description: Overall sentiment toward the main product discussed enum: - positive - neutral - negative ``` ## Quickstart ### Extract from an Online Video Use this when you have a public video URL: ```python Python theme={null} from vidnavigator import VidNavigatorClient client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY resp = client.extract_video_data( video_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", what_to_extract="Focus on product names, pricing, and the speaker's overall verdict", schema={ "products_mentioned": { "type": "Array", "description": "Products mentioned in the video", "items": {"type": "String", "description": "Product name"}, }, "pricing_signals": { "type": "Array", "description": "Any price points, pricing plans, or cost references", "items": {"type": "String", "description": "Pricing detail"}, }, "verdict": { "type": "String", "description": "Final recommendation or overall verdict from the speaker", }, }, ) print(resp.data) # a dict shaped like your schema print(resp.video_info.title) # the video's metadata ``` ```javascript JavaScript theme={null} import { VidNavigatorClient } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY }); const { data, video_info } = await vn.extractVideoData({ video_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', what_to_extract: "Focus on product names, pricing, and the speaker's overall verdict", schema: { products_mentioned: { type: 'Array', description: 'Products mentioned in the video', items: { type: 'String', description: 'Product name' }, }, pricing_signals: { type: 'Array', description: 'Any price points, pricing plans, or cost references', items: { type: 'String', description: 'Pricing detail' }, }, verdict: { type: 'String', description: 'Final recommendation or overall verdict from the speaker', }, }, }); console.log(data); // an object shaped like your schema console.log(video_info?.title); // the video's metadata ``` ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/extract/video" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "what_to_extract": "Focus on product names, pricing, and the speaker''s overall verdict", "schema": { "products_mentioned": { "type": "Array", "description": "Products mentioned in the video", "items": { "type": "String", "description": "Product name" } }, "pricing_signals": { "type": "Array", "description": "Any price points, pricing plans, or cost references", "items": { "type": "String", "description": "Pricing detail" } }, "verdict": { "type": "String", "description": "Final recommendation or overall verdict from the speaker" } } }' ``` The 2.1.0 SDKs run video extraction through [`/extract/video/async`](/api-reference/endpoint/extract-video-async), so videos of any length work with the same call. To run many extractions in parallel, use `client.submit_extract_video_data(...)` (Python) or `vn.extractVideo.submit(...)` (JavaScript). The SDK result contains the extracted `data` and the video's metadata in `video_info` (`resp.video_info` in Python, `video_info` in JavaScript). ### Extract from an Uploaded File Use this when the transcript already lives in your VidNavigator workspace: ```python Python theme={null} resp = client.extract_file_data( file_id="file_abc123", what_to_extract="Extract action items and owners from this meeting recording", schema={ "action_items": { "type": "Array", "description": "Tasks agreed on during the meeting", "items": {"type": "String", "description": "Action item"}, }, "owners": { "type": "Array", "description": "People assigned to specific tasks", "items": {"type": "String", "description": "Person name and task if available"}, }, "deadline_mentions": { "type": "Array", "description": "Any due dates or timing commitments mentioned", "items": {"type": "String", "description": "Deadline detail"}, }, }, ) print(resp.data) ``` ```javascript JavaScript theme={null} const { data } = await vn.extractFileData({ file_id: 'file_abc123', what_to_extract: 'Extract action items and owners from this meeting recording', schema: { action_items: { type: 'Array', description: 'Tasks agreed on during the meeting', items: { type: 'String', description: 'Action item' }, }, owners: { type: 'Array', description: 'People assigned to specific tasks', items: { type: 'String', description: 'Person name and task if available' }, }, deadline_mentions: { type: 'Array', description: 'Any due dates or timing commitments mentioned', items: { type: 'String', description: 'Deadline detail' }, }, }, }); console.log(data); ``` ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/extract/file" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_id": "file_abc123", "what_to_extract": "Extract action items and owners from this meeting recording", "schema": { "action_items": { "type": "Array", "description": "Tasks agreed on during the meeting", "items": { "type": "String", "description": "Action item" } }, "owners": { "type": "Array", "description": "People assigned to specific tasks", "items": { "type": "String", "description": "Person name and task if available" } }, "deadline_mentions": { "type": "Array", "description": "Any due dates or timing commitments mentioned", "items": { "type": "String", "description": "Deadline detail" } } } }' ``` ## When to Use `what_to_extract` Your schema defines the output shape. `what_to_extract` tells the model where to focus. Use it when: * the transcript covers multiple topics and you care about one of them * you want the model to prioritize a specific lens such as pricing, claims, objections, or action items * the field descriptions are correct but still too broad Good examples: * `Focus on feature claims, competitor mentions, and pricing strategy.` * `Extract only factual claims and cited statistics. Ignore jokes and off-topic banter.` * `Prioritize buyer intent, objections, and next steps from the sales call.` ## Schema Rules Your schema must follow these limits: * maximum `10` root fields * maximum `3` nesting levels * maximum `10` subfields per `Object` * supported field types: `String`, `Number`, `Boolean`, `Integer`, `Array`, `Object`, `Enum` * every field must include both `type` and `description` Write descriptions like instructions, not labels. `Primary pricing strategy discussed, in one sentence` is much better than `pricing`. ## Best Practices That Improve Results ### 1. Be specific in field descriptions Weak: ```json theme={null} { "topic": { "type": "String", "description": "Topic" } } ``` Better: ```json theme={null} { "primary_topic": { "type": "String", "description": "Primary topic discussed in the video, in 5 to 10 words" } } ``` ### 2. Use enums when the answer should come from a fixed list If a field is really a classification, use `Enum` instead of `String`. That makes downstream automation much easier. ### 3. Start simple, then expand Do not begin with a 10-field nested schema unless you already know it works. Start with the 2-3 fields that matter most, test them on a few videos, then add more. ### 4. Write descriptions in the output language you want The response is returned in the same language as your schema descriptions. If you write field descriptions in French, the output will also come back in French. ## Ready-to-Use Ideas Common extraction patterns include: * lead generation: companies, buyer intent, pain points, next steps * market research: competitor mentions, feature claims, pricing strategy, objections * creator analysis: hooks, quotes, sponsored mentions, content format * RAG ingestion: dense summary, entities, claims, topics, language code * fact-checking: claims, statistics, cited sources, controversy markers ## Online Videos vs. Uploaded Files ### `POST /extract/video` Choose this endpoint when: * you already have a public video URL * you want VidNavigator to fetch the transcript for you * you want automatic speech-to-text fallback for supported non-YouTube videos Important behavior: * `transcribe=true` by default * automatic transcription applies to non-YouTube platforms only * YouTube relies on platform captions and cannot use speech-to-text fallback through this endpoint * when speech-to-text runs, media longer than 10 minutes must go through `POST /extract/video/async` ### `POST /extract/file` Choose this endpoint when: * the media is already uploaded to your VidNavigator account * you want to extract from private or internal content * you already completed transcription earlier in your workflow Typical file workflow: 1. Upload the file. 2. Generate a transcript if needed. 3. Call `/v1/extract/file` with the resulting `file_id`. ## Billing and Performance Each extraction consumes `analysis_request` units. One unit covers up to **15,000 total tokens**; longer transcripts scale with: `ceil(total_tokens / 15000)` A few practical notes: * the first call for a new schema includes prompt compilation overhead * repeated calls with the same schema reuse the cached plan for 2 hours * `extract/video` may also trigger `transcription_hour` billing when auto-transcription is used * failed requests are reverted and are not billed ## Troubleshooting ### `transcript_not_available` This usually means the source does not have a usable transcript and auto-transcription is either disabled or unavailable. * For non-YouTube URLs, keep `transcribe=true` on `/extract/video`. * For uploaded files, transcribe the file before calling `/extract/file`. ### `video_too_long` The video needs speech-to-text and is longer than 10 minutes. Send the same request to [`/v1/extract/video/async`](/api-reference/endpoint/extract-video-async) and collect the result by polling or webhook. ### `invalid_schema` This usually means a field is missing `type` or `description`, nesting is too deep, or the schema exceeds the limits above. ### Results are too broad Tighten the schema descriptions and add a more targeted `what_to_extract` instruction. ### First request is slower than later requests That is expected when a schema is new. The compiled extraction plan is cached for 2 hours, so repeated schemas are faster. ## Next Steps * [Extract from an online video](/api-reference/endpoint/extract-video) * [Extract from an uploaded file](/api-reference/endpoint/extract-file) * [Upload a file first](/api-reference/endpoint/upload-file) * Schema files, token usage and batch extraction with the [Python](/sdks/python#structured-extraction) and [JavaScript](/sdks/javascript#structured-extraction) SDKs # Getting Transcripts Source: https://docs.vidnavigator.com/guides/getting-transcripts Learn how to retrieve transcripts from online videos using the VidNavigator API and SDKs. ## Overview VidNavigator is the ultimate video to text solution for extracting subtitles and captions from online videos, giving you all the essential information at your fingertips! Need full transcripts, timestamps, or video metadata? We've got it all. The JSON output is ready for instant integration into AI-powered applications. ## Transcript Endpoint VidNavigator provides a **single transcript endpoint** for every supported platform, including YouTube. The [`/transcript`](/api-reference/endpoint/transcript) endpoint auto-detects the platform from the URL and routes internally. This replaces the old, separate `/youtube/transcript` endpoint — YouTube URLs now go through the same `/transcript` endpoint as everything else. For **Instagram**, use [`/transcribe`](/api-reference/endpoint/transcribe) (speech-to-text) instead. ## Billing Billing depends on the URL: * **YouTube** transcript retrieval (or `metadata_only: true`) consumes `residential_request` usage — the fetch routes through the residential proxy. * **Non-YouTube** transcript retrieval consumes `standard_request` usage (some platforms internally require residential and are billed accordingly). Pass `include_usage=true` to receive a `usage` block disclosing exactly what was charged. ## Prerequisites * A valid VidNavigator API key. * The [Python](/sdks/python) or [JavaScript](/sdks/javascript) SDK, version 2.1.0: ```bash Python theme={null} pip install vidnavigator==2.1.0 ``` ```bash JavaScript theme={null} npm install vidnavigator@2.1.0 ``` ## Retrieving a Transcript Use `get_transcript` (Python) or `getTranscript` (JavaScript) for **any** platform: YouTube, TikTok, X/Twitter, Facebook, Vimeo, and more. ### Retrieving a YouTube Transcript ```python Python theme={null} from vidnavigator import VidNavigatorClient, VidNavigatorError client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY try: result = client.get_transcript( video_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" ) print("Video Title:", result.data.video_info.title) print("Available Languages:", result.data.video_info.available_languages) # Each item in the transcript is a segment with text, start, and end times for segment in result.data.transcript: print(f"[{segment.start:.2f}s - {segment.end:.2f}s] {segment.text}") except VidNavigatorError as e: print(f"An error occurred: {e}") ``` ```javascript JavaScript theme={null} import { VidNavigatorClient, VidNavigatorError } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY, }); try { const { video_info, transcript } = await vn.getTranscript({ video_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', }); console.log('Video Title:', video_info.title); console.log('Available Languages:', video_info.available_languages); // Each item in the transcript is an object with text, start, and end properties for (const segment of transcript) { console.log(`[${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s] ${segment.text}`); } } catch (error) { if (error instanceof VidNavigatorError) { console.error(`An error occurred: ${error.message}`); } else { throw error; } } ``` Pass `transcript_text=True` (Python) or `transcript_text: true` (JavaScript) to get the whole transcript as a single string instead of timed segments, and `language` to request a specific caption language (ISO 639-1, e.g. `"fr"`). ### Retrieving Transcripts from Other Platforms The exact same method works for non-YouTube platforms (TikTok, X/Twitter, Facebook, Vimeo, etc.): ```python Python theme={null} result = client.get_transcript( video_url="https://twitter.com/user/status/123456789" ) print("Video Title:", result.data.video_info.title) for segment in result.data.transcript: print(f"[{segment.start:.2f}s - {segment.end:.2f}s] {segment.text}") ``` ```javascript JavaScript theme={null} const { video_info, transcript } = await vn.getTranscript({ video_url: 'https://twitter.com/user/status/123456789', }); console.log('Video Title:', video_info.title); for (const segment of transcript) { console.log(`[${segment.start.toFixed(2)}s - ${segment.end.toFixed(2)}s] ${segment.text}`); } ``` ## Getting Metadata Only If you only need video information without the transcript, use the `metadata_only` parameter. This is faster and still counts as usage: ```python Python theme={null} # YouTube (metadata_only still routes through the residential proxy → residential_request) result = client.get_transcript( video_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", metadata_only=True, ) print(result.data.video_info.title, result.data.video_info.duration) # Other platforms → standard_request result = client.get_transcript( video_url="https://twitter.com/user/status/123456789", metadata_only=True, ) ``` ```javascript JavaScript theme={null} // YouTube (metadata_only still routes through the residential proxy → residential_request) const { video_info } = await vn.getTranscript({ video_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', metadata_only: true, }); console.log(video_info.title, video_info.duration); // Other platforms → standard_request await vn.getTranscript({ video_url: 'https://twitter.com/user/status/123456789', metadata_only: true, }); ``` ## Graceful Fallback to Metadata If you want to get metadata even when a transcript isn't available (instead of receiving a 404 error), use the `fallback_to_metadata` parameter: ```python Python theme={null} result = client.get_transcript( video_url="https://www.youtube.com/watch?v=dQw4w9WgXcQ", fallback_to_metadata=True, ) # If the transcript is unavailable, you still get video_info, # but the transcript is empty ``` ```javascript JavaScript theme={null} const { video_info, transcript } = await vn.getTranscript({ video_url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', fallback_to_metadata: true, }); // If the transcript is unavailable, you still get video_info, // but the transcript is empty ``` The `metadata_only` parameter takes precedence over `fallback_to_metadata`. If both are set, only metadata will be returned. ## When a Transcript Isn't Available Sometimes a platform doesn't expose captions we can fetch directly (e.g., many Instagram Reels). In that case, the transcript endpoint will return an error such as `404 transcript_not_available`. When this happens you can **fall back to speech-to-text**, which generates a fresh transcript from the audio. Speech-to-text supports **Instagram, X (Twitter), Facebook, TikTok, Dailymotion, Loom, and Vimeo** URLs. YouTube videos should use the transcript endpoint instead. ```python Python theme={null} result = client.transcribe_video( video_url="https://www.instagram.com/reel/C86ZvEaqRmo/" ) print("Transcript length:", len(result.data.transcript)) print("First line:", result.data.transcript[0].text) ``` ```javascript JavaScript theme={null} const { transcript } = await vn.transcribe('https://www.instagram.com/reel/C86ZvEaqRmo/'); console.log(`Transcript has ${transcript.length} segments`); console.log('First line:', transcript[0].text); ``` A common pattern is to try captions first, since they are faster and cheaper, and fall back to speech-to-text: ```python Python theme={null} from vidnavigator import BadRequestError, NotFoundError def best_transcript(url): try: return client.get_transcript(video_url=url, transcript_text=True).data.transcript except (NotFoundError, BadRequestError): # no captions, or a platform without captions return client.transcribe_video(video_url=url, transcript_text=True).data.transcript ``` ```javascript JavaScript theme={null} import { BadRequestError, NotFoundError } from 'vidnavigator'; async function bestTranscript(url) { try { return (await vn.getTranscript({ video_url: url, transcript_text: true })).transcript; } catch (error) { if (error instanceof NotFoundError || error instanceof BadRequestError) { return (await vn.transcribe({ video_url: url, transcript_text: true })).transcript; } throw error; } } ``` ## Long Videos (over 10 minutes) The synchronous [`/transcribe`](/api-reference/endpoint/transcribe) endpoint accepts media up to **10 minutes**; longer media is rejected with `400 video_too_long`. The async endpoint [`POST /transcribe/async`](/api-reference/endpoint/transcribe-async) has no duration cap. **The 2.1.0 SDKs always use the async endpoint**, so `transcribe_video` / `transcribe` work on videos of any length with no extra code: they submit the job, poll until it finishes, and return the result. To run many transcriptions in parallel, submit them all first and collect the results afterwards: ```python Python theme={null} urls = [ "https://www.facebook.com/watch/?v=1234567890", "https://vimeo.com/123456789", ] jobs = [client.submit_transcribe_video(video_url=url) for url in urls] print([job.task_id for job in jobs]) # save these to resume later with client.resume_job("transcribe", task_id) for url, job in zip(urls, jobs): result = job.result() # waits until this job is done print(url, result.data.transcript[0].text) ``` ```python Python (asyncio) theme={null} import asyncio from vidnavigator import AsyncVidNavigatorClient # pip install "vidnavigator[async]" urls = [ "https://www.facebook.com/watch/?v=1234567890", "https://vimeo.com/123456789", ] async def main(): async with AsyncVidNavigatorClient() as client: results = await asyncio.gather( *(client.transcribe_video(video_url=url) for url in urls), return_exceptions=True, # one failed job doesn't cancel the others ) for url, result in zip(urls, results): print(url, result if isinstance(result, Exception) else result.data.transcript[0].text) asyncio.run(main()) ``` ```javascript JavaScript theme={null} const urls = [ 'https://www.facebook.com/watch/?v=1234567890', 'https://vimeo.com/123456789', ]; const jobs = await Promise.all(urls.map((url) => vn.transcribe.submit(url))); console.log(jobs.map((job) => job.task_id)); // save these to resume later with vn.transcribe.resume(taskId) const results = await Promise.all(jobs.map((job) => job.result())); results.forEach((r, i) => console.log(urls[i], r.transcript[0].text)); ``` See [Async Jobs](/guides/async-jobs) for the raw HTTP workflow, timeouts and webhooks. ## Next Steps Now that you have the transcript, you can: * [Analyze the video](/guides/analyzing-videos) for deeper insights. * [Search the video's content](/guides/searching-videos) with natural language queries. * Browse every SDK method in the [Python](/sdks/python) and [JavaScript](/sdks/javascript) references. # Searching Through Videos Source: https://docs.vidnavigator.com/guides/searching-videos Learn how to use natural language to search for specific moments within a single video or across a library of videos. ## Overview VidNavigator supports two distinct search workflows: global platform search discovers videos on YouTube or TikTok, while semantic (vector) search finds relevant information in already indexed YouTube channel content and uploaded files. ## Prerequisites * A valid VidNavigator API key. * The [Python](/sdks/python) or [JavaScript](/sdks/javascript) SDK, version 2.1.0 (`pip install vidnavigator==2.1.0` or `npm install vidnavigator@2.1.0`). ## Billing Billing depends on which search you run: * **Semantic search over indexed content** consumes one `search_request` per call. This includes indexed YouTube channel search and file search (`/search/file`); file search can also bill `analysis_request` if the AI analysis pass runs. 1 credit covers 1,000 semantic searches. * **Global YouTube search** (`/youtube/search`) does **not** bill `search_request`. It bills one `residential_request` per candidate video analyzed, plus a single consolidated `analysis_request` (`ceil(total_tokens / 15000)`). See [Search YouTube](/api-reference/endpoint/youtube-search). * **TikTok keyword search** (`/tiktok/search`) does **not** bill `search_request`; it bills per residential page fetched. See [TikTok Search](/api-reference/endpoint/tiktok-search). ## Searching Videos ### Searching Across YouTube This example searches YouTube for videos that match a query, via the `/youtube/search` endpoint. The results are ranked with an AI analysis of each video's transcript. ```python Python theme={null} from vidnavigator import VidNavigatorClient, VidNavigatorError client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY try: results = client.search_youtube( query="What are the best practices for React development?", focus="relevance", start_year=2022, max_results=5, # caps how many candidates are analyzed, and the cost ) print(f"Found {results.data.total_found} relevant videos:") for video in results.data.results: print(f"- {video.title} ({video.url}), score: {video.relevance_score}") print(f" {video.transcript_summary}") except VidNavigatorError as e: print(f"An error occurred: {e}") ``` ```javascript JavaScript theme={null} import { VidNavigatorClient, VidNavigatorError } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY, }); try { const results = await vn.searchYouTube({ query: 'What are the best practices for React development?', focus: 'relevance', start_year: 2022, max_results: 5, // caps how many candidates are analyzed, and the cost }); console.log(`Found ${results.total_found} relevant videos:`); for (const video of results.results) { console.log(`- ${video.title} (${video.url}), score: ${video.relevance_score}`); console.log(` ${video.transcript_summary}`); } // If credits run out mid-search, the SDK returns the videos analyzed so far if (results.status === 'partial') { console.log('Partial results:', results.error_code); } } catch (error) { if (error instanceof VidNavigatorError) { console.error(`An error occurred: ${error.message}`); } else { throw error; } } ``` `focus` accepts `relevance` (default), `popularity` or `brevity`. You can also filter with `end_year` and `duration` (in seconds). `search_videos` / `searchVideos` still work in the 2.1.0 SDKs as deprecated aliases. Use `search_youtube` / `searchYouTube` in new code. ### Searching Your Uploaded Files This example searches across the files you have uploaded to VidNavigator. Each result includes `namespace_ids` and `namespaces` showing which namespaces the file belongs to, plus the timestamps where the matching content appears. ```python Python theme={null} try: results = client.search_files(query="Customer feedback on pricing") print(f"Found {results.data.total_found} relevant files:") for file_result in results.data.results: ns_names = [ns.name for ns in file_result.namespaces or []] print(f"- {file_result.name} (score: {file_result.relevance_score}, namespaces: {ns_names})") print(f" Timestamps: {file_result.timestamps}") except VidNavigatorError as e: print(f"An error occurred: {e}") ``` ```javascript JavaScript theme={null} try { const results = await vn.searchFiles({ query: 'Customer feedback on pricing' }); console.log(`Found ${results.total_found} relevant files:`); for (const fileResult of results.results) { const nsNames = (fileResult.namespaces ?? []).map((ns) => ns.name); console.log(`- ${fileResult.name} (score: ${fileResult.relevance_score}, namespaces: ${nsNames.join(', ')})`); console.log(` Answer: ${fileResult.query_answer}`); } } catch (error) { if (error instanceof VidNavigatorError) { console.error(`An error occurred: ${error.message}`); } else { throw error; } } ``` ### Filtering File Search by Namespace You can restrict search to specific namespaces by passing `namespace_ids`. If omitted, all namespaces are searched. ```python Python theme={null} results = client.search_files( query="Customer feedback on pricing", namespace_ids=["64a1b2c3d4e5f6789abc0002"], ) ``` ```javascript JavaScript theme={null} const results = await vn.searchFiles({ query: 'Customer feedback on pricing', namespace_ids: ['64a1b2c3d4e5f6789abc0002'], }); ``` ## Next Steps * Search TikTok by keyword or collect a creator's videos: [Searching TikTok](/guides/tiktok-search). * Upload your own recordings to make them searchable: [Uploading and Processing Files](/guides/uploading-videos). # Searching TikTok Source: https://docs.vidnavigator.com/guides/tiktok-search Collect TikTok videos from a profile or by keyword using the asynchronous TikTok endpoints, then poll for paginated results. ## Overview VidNavigator offers two ways to collect TikTok videos: Walk a single creator's account and return their videos, filtered by date and likes. Search across TikTok by keyword and return matching videos from many creators. Both are **asynchronous**: you submit a task, get a `task_id` back immediately, then poll a `GET` endpoint until the task is `completed`. Results are retained for about 1 hour, and large result sets can be downloaded as a single JSON file via a temporary `download_url`. The [Python](/sdks/python#tiktok) and [JavaScript](/sdks/javascript#tiktok) SDKs (v2.1.0) wrap the submit-and-poll flow in a single call. Each section below starts with the SDK version, followed by the raw HTTP workflow (cURL, Python `requests`, and `fetch`) if you'd rather call the API directly. ## Prerequisites * A valid VidNavigator API key. * Optional: the SDK, version 2.1.0 (`pip install vidnavigator==2.1.0` or `npm install vidnavigator@2.1.0`). ## Billing Both endpoints bill in the background worker, so **usage is disclosed on the polling `GET`** (pass `include_usage=true` there), not at submit time. | Endpoint | What you're billed | | - | - | | Profile scrape (`/tiktok/profile`) | `standard_request` per TikTok page fetched (`residential_request` when a residential proxy is used). | | Keyword search (`/tiktok/search`) | `residential_request` per page fetched (`pages_fetched × residential_request`). An N-slice search bills up to **N×** the pages. **No `search_request`.** | See [Usage & Costs](/api-reference/usage-costs) for credit conversions. ## Searching a TikTok Profile Send a public `profile_url` and optional filters. `max_posts` and `after_datetime` let the scraper stop paginating early. ### With the SDK One call submits the scrape, waits for it to finish, and returns the videos. In JavaScript you get **every** matching video (the SDK walks all pages); in Python you get the first page of `limit` videos plus the pagination cursor. ```python Python theme={null} from vidnavigator import VidNavigatorClient client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY result = client.scrape_tiktok_profile( profile_url="https://www.tiktok.com/@tiktok", max_posts=50, after_datetime="2024-01-01", min_likes=10000, limit=50, # size of the returned page ) for video in result.data.videos or []: print(video.published_at, video.likes, video.url) # published_at is a datetime # Read the remaining pages, if any task_id, pagination = result.data.task_id, result.data.pagination while pagination and pagination.has_next: page = client.get_tiktok_profile_scrape(task_id, cursor=pagination.next_cursor, limit=50) for video in page.data.videos or []: print(video.published_at, video.likes, video.url) pagination = page.data.pagination ``` ```javascript JavaScript theme={null} import { VidNavigatorClient } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY }); const task = await vn.tiktokProfile({ profile_url: 'https://www.tiktok.com/@tiktok', max_posts: 50, after_datetime: '2024-01-01', min_likes: 10000, }); console.log(`Collected ${task.videos.length} videos`, task.stats); for (const video of task.videos.slice(0, 5)) { console.log(video.published_at?.toISOString(), video.likes, video.url); // published_at is a Date } ``` To start the scrape without waiting, use `client.submit_tiktok_profile_scrape(...)` (Python) or `vn.tiktokProfile.submit(...)` (JavaScript) and collect it later with `job.result()`. Both accept a `webhook_url` to be [notified](/webhooks) when the scrape finishes. ### With raw HTTP #### 1. Submit the scrape ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tiktok/profile" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "profile_url": "https://www.tiktok.com/@tiktok", "max_posts": 50, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 10000 }' ``` ```python Python theme={null} import requests BASE = "https://api.vidnavigator.com/v1" HEADERS = {"X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json"} resp = requests.post( f"{BASE}/tiktok/profile", headers=HEADERS, json={ "profile_url": "https://www.tiktok.com/@tiktok", "max_posts": 50, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 10000, }, ) task_id = resp.json()["data"]["task_id"] print("Task ID:", task_id) ``` ```javascript JavaScript theme={null} const BASE = "https://api.vidnavigator.com/v1"; const HEADERS = { "X-API-Key": "YOUR_API_KEY", "Content-Type": "application/json", }; const submit = await fetch(`${BASE}/tiktok/profile`, { method: "POST", headers: HEADERS, body: JSON.stringify({ profile_url: "https://www.tiktok.com/@tiktok", max_posts: 50, after_datetime: "2024-01-01T00:00:00Z", min_likes: 10000, }), }); const { data } = await submit.json(); const taskId = data.task_id; console.log("Task ID:", taskId); ``` #### 2. Poll until completed and page through results Poll `GET /tiktok/profile/{task_id}` while `task_status` is `processing`. Once `completed`, walk the pages using the `next_cursor` from `data.pagination`. ```python Python theme={null} import time def get_profile_page(task_id, cursor=None, limit=50): params = {"limit": limit} if cursor: params["cursor"] = cursor r = requests.get(f"{BASE}/tiktok/profile/{task_id}", headers=HEADERS, params=params) return r.json()["data"] # Wait for completion while True: data = get_profile_page(task_id, limit=1) if data["task_status"] != "processing": break time.sleep(3) if data["task_status"] == "failed": raise RuntimeError((data.get("error") or {}).get("message", "scrape failed")) # Page through all videos videos, cursor = [], None while True: page = get_profile_page(task_id, cursor=cursor, limit=100) videos.extend(page["videos"]) pagination = page["pagination"] if not pagination["has_next"]: break cursor = pagination["next_cursor"] print(f"Collected {len(videos)} videos") for v in videos[:5]: print(v["published_at"], v["likes"], v["url"]) ``` ```javascript JavaScript theme={null} async function getProfilePage(taskId, { cursor, limit = 50, includeUsage = false } = {}) { const params = new URLSearchParams({ limit: String(limit) }); if (cursor) params.set("cursor", cursor); if (includeUsage) params.set("include_usage", "true"); const r = await fetch(`${BASE}/tiktok/profile/${taskId}?${params}`, { headers: HEADERS }); return (await r.json()).data; } // Wait for completion let status; do { status = await getProfilePage(taskId, { limit: 1 }); if (status.task_status === "processing") await new Promise((res) => setTimeout(res, 3000)); } while (status.task_status === "processing"); if (status.task_status === "failed") throw new Error(status.error?.message || "scrape failed"); // Page through all videos const videos = []; let cursor = null; let hasNext = true; while (hasNext) { const page = await getProfilePage(taskId, { cursor, limit: 100 }); videos.push(...page.videos); hasNext = page.pagination.has_next; cursor = page.pagination.next_cursor; } console.log(`Collected ${videos.length} videos`); ``` For very large profiles, skip pagination and download the whole result at once: once the task is `completed`, `data.download_url` holds a short-lived signed URL to the full JSON. Call the `GET` endpoint again to mint a fresh URL if it expires. ## Searching TikTok by Keyword Keyword search returns videos across many creators, sorted by `published_at` (newest first) unless you set `sort_by` (`relevance`, `most_liked`, `newest`). Use `published_within` (`past_24_hours`, `this_week`, `this_month`, `last_3_months`, `last_6_months`) or `after_datetime` / `before_datetime` for time windows, and `parallel_search_slices` (1–4) to lift the result ceiling (\~115–140 items per slice) — most useful on searches without a date window. ### With the SDK ```python Python theme={null} result = client.search_tiktok( query="ai tools", sort_by="most_liked", published_within="this_month", min_likes=1000, limit=50, include_usage=True, ) stats = result.data.stats print(f"{stats.results_count} results, sorted by {stats.sort_by} within {stats.published_within}") for item in result.data.results or []: print(item.published_at, item.stats.likes if item.stats else None, item.url) if result.usage: print("Credits charged:", result.usage.total_credits) ``` ```javascript JavaScript theme={null} const task = await vn.tiktokSearch( { query: 'ai tools', sort_by: 'most_liked', published_within: 'this_month', min_likes: 1000, }, { include_usage: true } ); console.log(`${task.results.length} results, sorted by ${task.stats?.sort_by} within ${task.stats?.published_within}`); for (const item of task.results.slice(0, 5)) { console.log(item.published_at?.toISOString(), item.stats?.likes, item.url); } if (task.usage) console.log('Credits charged:', task.usage.total_credits); ``` Python pages through further results with `client.get_tiktok_search(task_id, cursor=...)`, exactly like the profile example. The JavaScript call already returns every result. ### With raw HTTP #### 1. Submit the search ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tiktok/search" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "ai tools", "parallel_search_slices": 2, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 1000 }' ``` ```python Python theme={null} resp = requests.post( f"{BASE}/tiktok/search", headers=HEADERS, json={ "query": "ai tools", "parallel_search_slices": 2, "after_datetime": "2024-01-01T00:00:00Z", "min_likes": 1000, }, ) task_id = resp.json()["data"]["task_id"] print("Task ID:", task_id) ``` ```javascript JavaScript theme={null} const submit = await fetch(`${BASE}/tiktok/search`, { method: "POST", headers: HEADERS, body: JSON.stringify({ query: "ai tools", parallel_search_slices: 2, after_datetime: "2024-01-01T00:00:00Z", min_likes: 1000, }), }); const taskId = (await submit.json()).data.task_id; console.log("Task ID:", taskId); ``` #### 2. Poll, paginate, and read usage The polling flow is identical to the profile scrape — just swap the path to `/tiktok/search/{task_id}` and read `data.results` instead of `data.videos`. Pass `include_usage=true` once the task is `completed` to see what was billed. ```python Python theme={null} import time def get_search_page(task_id, cursor=None, limit=50, include_usage=False): params = {"limit": limit} if cursor: params["cursor"] = cursor if include_usage: params["include_usage"] = "true" r = requests.get(f"{BASE}/tiktok/search/{task_id}", headers=HEADERS, params=params) return r.json() # Wait for completion while True: body = get_search_page(task_id, limit=1) if body["data"]["task_status"] != "processing": break time.sleep(3) # First completed page, with usage disclosure body = get_search_page(task_id, limit=50, include_usage=True) data = body["data"] print(f"{data['stats']['results_count']} results across {data['stats']['pages_fetched']} pages") for item in data["results"][:5]: print(item["published_at"], item["stats"]["likes"], item["url"]) # Usage is at the top level of the response if "usage" in body: print("Credits charged:", body["usage"]["total_credits"]) ``` ```javascript JavaScript theme={null} async function getSearchPage(taskId, { cursor, limit = 50, includeUsage = false } = {}) { const params = new URLSearchParams({ limit: String(limit) }); if (cursor) params.set("cursor", cursor); if (includeUsage) params.set("include_usage", "true"); const r = await fetch(`${BASE}/tiktok/search/${taskId}?${params}`, { headers: HEADERS }); return await r.json(); } // Wait for completion let body; do { body = await getSearchPage(taskId, { limit: 1 }); if (body.data.task_status === "processing") await new Promise((res) => setTimeout(res, 3000)); } while (body.data.task_status === "processing"); // First completed page with usage disclosure body = await getSearchPage(taskId, { limit: 50, includeUsage: true }); const { results, stats } = body.data; console.log(`${stats.results_count} results across ${stats.pages_fetched} pages`); results.slice(0, 5).forEach((item) => console.log(item.published_at, item.stats.likes, item.url) ); if (body.usage) console.log("Credits charged:", body.usage.total_credits); ``` Don't pass `include_usage` on the submit (`POST`) call — it's ignored because billing happens after the `202` response. Pass it on the polling `GET`, and only expect a `usage` block once `task_status=completed` (failed tasks have all charges refunded). ## Choosing Between Profile and Keyword Search | Use case | Endpoint | | - | - | | Collect one creator's catalog | Profile scrape | | Track a hashtag, topic, or trend across creators | Keyword search | | Filter by date range and likes | Both (add `after_datetime` / `before_datetime`, `min_likes` / `max_likes`) | | Filter by views | Keyword search (`min_views` / `max_views`) | | Maximize coverage of a keyword | Keyword search with `parallel_search_slices` up to `4` (no date window) | | Most popular recent videos for a keyword | Keyword search with `sort_by: "most_liked"` and `published_within` | ## Next Steps * [TikTok Profile Scrape reference](/api-reference/endpoint/tiktok-profile) * [TikTok Keyword Search reference](/api-reference/endpoint/tiktok-search) * [Usage & Costs](/api-reference/usage-costs) # Tweet Claim Analysis Source: https://docs.vidnavigator.com/guides/tweet-claim-analysis Understand what an X post actually means by combining the original tweet, quoted tweet, and attached media. Most X APIs give you tweet text and metadata. That is useful for retrieval, but it often misses the actual message. On X, meaning is frequently split across the main tweet, the quoted tweet, and the media attached to one or both posts. VidNavigator's Tweet Claim Analysis endpoint is built for that exact problem. It reconstructs intent across all of those layers and returns one structured intelligence object you can use in verification, monitoring, analytics, or AI workflows. ## What This Guide Helps You Do By the end of this guide, you will know how to: * decide when tweet claim analysis is the right tool * send a valid `tweet_id` * understand what the API reads before it writes a result * use `final_statement`, `detailed_analysis`, and the classification axes correctly * plug the result into fact-checking, social monitoring, or clustering workflows ## When to Use This Endpoint Use tweet claim analysis when you care about what the author meant, not just what they typed. Good fits: * fact-checking and verification * narrative tracking across many posts * reputation and risk monitoring * clustering similar claims into dashboards * turning noisy social posts into structured AI-ready data If you only need raw tweet text, engagement metrics, or basic metadata, this endpoint is probably more powerful than you need. ## Why This Is Different from Basic Tweet Retrieval A normal tweet API can tell you: * the tweet text * the author * the timestamp * public metrics This endpoint goes further. It: * reads the original tweet * checks whether a quoted tweet changes or completes the meaning * analyzes media attached to the original tweet (videos are transcribed; images are described by a vision model) * analyzes media attached to the quoted tweet * returns one structured interpretation of the overall claim That is the difference between collecting posts and extracting usable intelligence. ## What the API Actually Returns `final_statement` is the normalized claim the author appears to be making after the full context is analyzed. `detailed_analysis` explains the claim, context, evidence, and stance in plain language. `topics` and `entities` surface the key subjects and named people/organizations/places for tagging and clustering. `claim_type`, `intent`, `tone`, `emotion`, and `authority` classify the original tweet text on fixed taxonomies. The classification axes are based on the original tweet text only. The broader fields such as `final_statement` and media summaries can reflect quoted content and attached media. ## How It Works 1. You send a numeric `tweet_id`. 2. VidNavigator fetches the original tweet. 3. If the tweet quotes another post, VidNavigator fetches that too. 4. If either layer includes video or audio, VidNavigator retrieves transcripts or falls back to speech-to-text when needed. Attached images are described by a vision model. 5. The full context is analyzed and returned as a structured claim object. This is especially useful when the visible text is vague, sarcastic, reactive, or incomplete without the quoted post or media evidence. ## Quickstart The endpoint accepts a tweet ID, not a full URL. For example, in: `https://x.com/user/status/1912345678901234567` the `tweet_id` is: `1912345678901234567` ### Basic Request ```bash cURL theme={null} curl -X POST "https://api.vidnavigator.com/v1/tweet/statement" \ -H "X-API-Key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "tweet_id": "1912345678901234567" }' ``` ```python Python theme={null} from vidnavigator import VidNavigatorClient client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY resp = client.get_tweet_statement(tweet_id="1912345678901234567") print(resp.data.final_statement) print(resp.data.detailed_analysis) print(resp.data.claim_type, resp.data.intent, resp.data.tone) print(resp.data.topics, resp.data.entities) ``` ```javascript JavaScript theme={null} import { VidNavigatorClient } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY }); const statement = await vn.tweetStatement('1912345678901234567'); console.log(statement.final_statement); console.log(statement.detailed_analysis); console.log(statement.claim_type, statement.intent, statement.tone); console.log(statement.topics, statement.entities); ``` The SDKs (v2.1.0) call the async variant `POST /v1/tweet/statement/async` for you, so tweets with long videos work with the same call. With raw HTTP, use the async endpoint if the tweet may carry a video longer than 10 minutes: same body, result by polling or webhook. See [Async Jobs](/guides/async-jobs). To analyze many tweets in parallel, submit them all first and collect the results afterwards: ```python Python theme={null} tweet_ids = ["1912345678901234567", "1912345678901234568"] jobs = [client.submit_tweet_statement(tweet_id=tid) for tid in tweet_ids] for tid, job in zip(tweet_ids, jobs): print(tid, job.result().data.final_statement) ``` ```javascript JavaScript theme={null} const tweetIds = ['1912345678901234567', '1912345678901234568']; const jobs = await Promise.all(tweetIds.map((id) => vn.tweetStatement.submit(id))); const statements = await Promise.all(jobs.map((job) => job.result())); statements.forEach((s, i) => console.log(tweetIds[i], s.final_statement)); ``` ## How to Read the Response ### `final_statement` This is the field most teams care about first. It answers: `What is this post really claiming once all context is taken into account?` Use it for: * human review * case summaries * analyst handoff * verification-ready claim normalization (use it directly as a search/verification query) ### `detailed_analysis` This is the explanation layer. It helps reviewers understand why the system interpreted the post the way it did. ### `topics` and `entities` Use these for filtering, tagging, grouping, alerting, or analytics. ### Media summaries `tweet_media_summary` and `quoted_media_summary` explain what the attached media contributed to the meaning, if media exists in either layer. For videos this is a transcript summary; for images it is a vision-model description (transcribed text, chart/graph data, recognizable people and logos, and any claims they make). ## Classification Axes The endpoint returns five classification axes for the original tweet text: * `claim_type`: what kind of post it is, such as `factual_claim`, `opinion`, or `news_sharing` * `intent`: what the author seems to be trying to do, such as `inform`, `persuade`, or `challenge` * `tone`: how the message is delivered, such as `serious`, `skeptical`, or `provocative` * `emotion`: the dominant emotional signal, such as `urgency`, `outrage`, or `hope_inspiration` * `authority`: what kind of backing the tweet appears to rely on, such as `data_driven`, `expert_led`, or `speculative` These labels are useful for routing, analytics, prioritization, and moderation workflows. ## Common Workflows ### Fact-checking Use `final_statement` as the normalized claim to verify — it doubles as a search query for reporting, source documents, or related video evidence. ### Reputation and crisis monitoring Track emotionally charged posts, accusations, or fast-moving narratives. The combination of `claim_type`, `tone`, `emotion`, and media summaries gives you more signal than raw keyword alerts. ### Narrative clustering Store `final_statement`, `topics`, and `entities` for every analyzed tweet. That makes it much easier to group similar claims even when the original wording differs. ### AI product integration Feed the output into dashboards, RAG pipelines, search systems, or internal tools that need clean structured social intelligence instead of raw tweet text. ## Billing and Media Notes Billing is **consolidated** — a single call may run several internal LLM calls (media extraction, image vision, and the main statement extraction) but produces just **one** `analysis_request` charge per request: * a text-only tweet usually consumes `analysis_request` units only * a tweet with attached media can also trigger `standard_request` or `residential_request` (per media URL fetched) and `transcription_hour` (if speech-to-text runs) * a quoted tweet with its own media can add another layer of processing * **image analysis is billed through the same `analysis_request` meter** (no separate meter) Analysis Request billing is dynamic: 1 `analysis_request` unit covers up to 15,000 total tokens, and larger contexts use `ceil(total_tokens / 15000)` units. In practice, this means some tweets are lightweight and some are multimodal analysis jobs. The endpoint handles that automatically, but it is useful to know why two tweets may not cost the same. ## Troubleshooting ### Invalid tweet ID Send only the numeric ID. Do not send the full X URL. ### Missing media summaries If no media exists, or the media could not be processed, `tweet_media_summary` and `quoted_media_summary` may be `null`. ### Unexpected classification Remember that the five classification axes are based on the original tweet text only, while `final_statement` reflects the broader contextual interpretation. ### `video_too_long` The tweet (or the tweet it quotes) carries a video longer than 10 minutes, the limit of the synchronous endpoint. Send the same request to [`/v1/tweet/statement/async`](/api-reference/endpoint/tweet-claim-analysis-async) and collect the result by polling or [webhook](/webhooks). ### Upstream fetch issues If X content cannot be fetched or parsed upstream, the endpoint may return an error such as `502`. ## Next Steps * [Tweet Claim Analysis API reference](/api-reference/endpoint/tweet-claim-analysis) * [Extract structured data from videos](/guides/data-extraction) # Uploading and Processing Files Source: https://docs.vidnavigator.com/guides/uploading-videos Learn how to upload your own video and audio files for transcription and analysis. ## Overview In addition to online videos, VidNavigator allows you to upload and process your own local audio and video files. This is useful for analyzing private content or files that are not publicly available on the web. **Supported formats:** mp4, webm, mov, avi, wmv, flv, mkv, m4a, mp3, mpeg, mpga, wav. ## Prerequisites * A valid VidNavigator API key. * The [Python](/sdks/python) or [JavaScript](/sdks/javascript) SDK, version 2.1.0 (`pip install vidnavigator==2.1.0` or `npm install vidnavigator@2.1.0`). ## Organizing Files with Namespaces You can organize your uploaded files into **namespaces** (like folders). When uploading, pass `namespace_ids` to assign the file to one or more namespaces. You can also manage namespaces later via the [namespace endpoints](/api-reference/endpoint/list-namespaces). All file responses (list, info, search) include `namespace_ids` and `namespaces` fields showing which namespaces each file belongs to. ## Billing Uploading and processing audio/video files consumes `transcription_hour` usage for speech-to-text. 1 credit covers 3 hours of video/audio transcription. Uploaded files also count toward your plan's storage quota. ## Uploading a File The upload is asynchronous by default: the API returns a file ID immediately and processes the file in the background. Pass `wait_for_completion` to block until processing has finished instead. ```python Python theme={null} from vidnavigator import VidNavigatorClient, VidNavigatorError client = VidNavigatorClient() # reads VIDNAVIGATOR_API_KEY try: response = client.upload_file( "path/to/your/video.mp4", wait_for_completion=True, # omit to return immediately namespace_ids=["ns_meetings"], # optional ) print(f"File ID: {response['file_id']}, status: {response['file_status']}") except FileNotFoundError: print("Error: The file was not found at the specified path.") except VidNavigatorError as e: print(f"An error occurred: {e}") ``` ```javascript JavaScript theme={null} import { VidNavigatorClient, VidNavigatorError } from 'vidnavigator'; const vn = new VidNavigatorClient({ apiKey: process.env.VIDNAVIGATOR_API_KEY, }); try { const upload = await vn.uploadFile({ filePath: 'path/to/your/video.mp4', wait_for_completion: true, // omit to return immediately namespace_ids: ['ns_meetings'], // optional }); console.log(`File ID: ${upload.file_id}, status: ${upload.file_status}`); } catch (error) { if (error instanceof VidNavigatorError) { console.error(`An error occurred: ${error.message}`); } else { throw error; // e.g. ENOENT when the file does not exist } } ``` ## Checking Processing Status Without `wait_for_completion`, poll the file until its status is no longer `processing`, then read its transcript: ```python Python theme={null} import time file_id = client.upload_file("path/to/your/interview.mp3")["file_id"] info = client.get_file(file_id).data.file_info while info.status == "processing": time.sleep(5) info = client.get_file(file_id).data.file_info if info.status == "failed": client.retry_file_processing(file_id) else: resp = client.get_file(file_id, transcript_text=True) print(resp.data.transcript) ``` ```javascript JavaScript theme={null} const { file_id } = await vn.uploadFile({ filePath: 'path/to/your/interview.mp3' }); let { file_info } = await vn.getFile(file_id); while (file_info.status === 'processing') { await new Promise((r) => setTimeout(r, 5000)); ({ file_info } = await vn.getFile(file_id)); } if (file_info.status === 'failed') { await vn.retryFileProcessing(file_id); } else { const { transcript } = await vn.getFile(file_id, { transcript_text: true }); console.log(transcript); } ``` ## Working with the File Once processed, a file works just like an online video: ```python Python theme={null} # Ask questions about it analysis = client.analyze_file(file_id=file_id, query="What action items were discussed?") print(analysis.data.transcript_analysis.query_answer.answer) # Extract structured data extraction = client.extract_file_data( file_id=file_id, schema={"action_items": {"type": "Array", "description": "Action items", "items": {"type": "String", "description": "One action item"}}}, ) print(extraction.data) # Manage it client.get_file_url(file_id) # signed URL to view or download the file client.get_files(limit=20, status="completed") client.delete_file(file_id) ``` ```javascript JavaScript theme={null} // Ask questions about it const { transcript_analysis } = await vn.analyzeFile({ file_id, query: 'What action items were discussed?' }); console.log(transcript_analysis.query_answer?.answer); // Extract structured data const { data } = await vn.extractFileData({ file_id, schema: { action_items: { type: 'Array', description: 'Action items', items: { type: 'String', description: 'One action item' } } }, }); console.log(data); // Manage it const { file_url } = await vn.getFileUrl(file_id); // signed URL to view or download the file const { files } = await vn.getFiles({ limit: 20, status: 'completed' }); await vn.deleteFile(file_id); ``` ## Next Steps * [Search the content of your uploaded files](/guides/searching-videos#searching-your-uploaded-files). * [Organize files into namespaces](/sdks/python#namespaces) with the SDK, or via the [namespace endpoints](/api-reference/endpoint/list-namespaces). * [Extract structured data](/guides/data-extraction) from the transcript using a custom schema. * See the [Upload File](/api-reference/endpoint/upload-file) and [File Info](/api-reference/endpoint/file-info) API references. # VidNavigator AI Source: https://docs.vidnavigator.com/index VidNavigator is an AI-powered video intelligence platform. Transcribe, analyze, extract data, and search across videos from YouTube, TikTok, Instagram, X/Twitter, Facebook, Vimeo, Loom, Dailymotion, and your own uploaded files — all through a single API. ## Overview VidNavigator gives you a single API to transcribe, analyze, extract structured data, and search across video content from all major platforms and your own uploaded files. Build applications that understand video content using state-of-the-art AI. ### Supported Platforms | Capability | YouTube | TikTok, Instagram, X/Twitter, Facebook, Vimeo, Loom, Dailymotion | Your Uploaded Files | | - | - | - | - | | **Transcription** | Yes | Yes | Yes (speech-to-text) | | **AI Analysis** | Yes | Yes | Yes | | **Data Extraction** | Yes | Yes | Yes | | **Platform Search** | Yes | TikTok | — | | **Semantic Search** | Indexed channels | — | Yes | Here are the core capabilities you can unlock with the VidNavigator API: Retrieve metadata and transcripts from YouTube, TikTok, Instagram, X/Twitter, Facebook, Vimeo, Loom, Dailymotion, and more. Upload your own audio/video files for speech-to-text transcription. Supports multiple languages with structured, timestamped output. Analyze video content to extract summaries, key topics, people, places, and insights. Ask specific questions about any video or uploaded file. Define a custom JSON schema and extract typed, structured data from any video transcript — ideal for automated pipelines and content cataloging. Discover videos with global YouTube and TikTok search, then analyze and rank the results. Search vector-indexed YouTube channel content and your uploaded file library to find precise moments and answers. ## Explore Our Resources Dive deeper into our platform with our SDKs, API reference, and detailed guides. Get started with our Python library for seamless integration. Use our JavaScript library for web and Node.js applications. Explore detailed documentation for every endpoint. Follow step-by-step guides for common use cases. # MCP Server Source: https://docs.vidnavigator.com/mcp-server Give Cursor, Claude Desktop, n8n or any MCP client the full VidNavigator API: video search, transcription, analysis, data extraction, TikTok and your files ## VidNavigator MCP Server The VidNavigator MCP (Model Context Protocol) server lets an AI assistant search, transcribe, analyze and extract data from online videos, and work with the files you uploaded, all from natural language. It is hosted at `https://api.vidnavigator.com/mcp/` and works with any MCP client that supports HTTP servers: Cursor, Claude Desktop, n8n, and others. AI-ranked YouTube search, with filters for year, duration and purpose (find videos that support or oppose a claim). Transcripts from YouTube, TikTok, X, Facebook, Vimeo and more, and speech-to-text for videos without captions, Instagram included, of any length. Summaries and Q\&A on any video, and structured data extracted into the fields you define. Keyword search with sort, date and popularity filters, and full profile scrapes. Extract the core claim of a tweet, including its attached video or images, ready to fact-check. List, search, analyze and extract data from the files you uploaded to VidNavigator. ## Quick Setup Get your API key from [VidNavigator Studio → API](https://vidnavigator.com/studio/api) first. The server accepts it as `Authorization: Bearer ` or as an `X-API-Key: ` header. ### Cursor Open Cursor → Settings → Tools and integration → Add MCP server, then paste: ```jsonc theme={null} { "mcpServers": { "vidnavigator": { "type": "http", "url": "https://api.vidnavigator.com/mcp/", "headers": { "Authorization": "Bearer YOUR_API_KEY_HERE" } } } } ``` Replace `YOUR_API_KEY_HERE` with your API key. Cursor MCP Configuration You can now ask things like: * "Find a video where Neil deGrasse Tyson explains gravity" * "Transcribe this Instagram reel and summarize it: [https://www.instagram.com/reel/](https://www.instagram.com/reel/)..." * "Find TikToks about AI tools posted in the last 24 hours" ### Claude Desktop The VidNavigator extension connects Claude Desktop to the hosted server, so you get every tool, and new ones appear without reinstalling it. Download `vidnavigator.dxt` from the [latest GitHub release](https://github.com/vidnavigator/vidnavigator-mcp-starter/releases/latest). Version 2.0.0 or later is required for the full tool set. Open Claude Desktop → Settings → Extensions → Install Extension, then select the downloaded `.dxt` file and follow the prompts. Claude Desktop Extensions Install Extension Enter your API key in the extension settings and enable the extension. Enable Extension VidNavigator appears in the tools menu of a new conversation. If you prefer not to install an extension, point Claude Desktop at the hosted server with [`mcp-remote`](https://www.npmjs.com/package/mcp-remote), which requires [Node.js](https://nodejs.org) 18 or later. Open Settings → Developer → Edit Config, add the following to `claude_desktop_config.json`, and restart Claude Desktop: ```json theme={null} { "mcpServers": { "vidnavigator": { "command": "npx", "args": [ "-y", "mcp-remote", "https://api.vidnavigator.com/mcp/", "--header", "X-API-Key:YOUR_API_KEY_HERE" ] } } } ``` Keep `X-API-Key:` and the key together with no space: on Windows, a space in a header argument splits it in two and the key is lost. ### n8n You can integrate VidNavigator into your own workflows with [n8n](https://n8n.io/), an open-source automation tool. Watch this video to learn how to set up an n8n workflow that connects to the VidNavigator MCP server, and follow the steps below.