vidnavigator
v1.0.5
Published
Official JavaScript SDK for the VidNavigator Developer API
Maintainers
Readme
VidNavigator SDK for JavaScript
The official JavaScript/TypeScript SDK for the VidNavigator Developer API. Transcribe, analyze, search, and extract structured data from video and audio — YouTube, Instagram, TikTok, X/Twitter, Vimeo, Facebook, Dailymotion, Loom, and your own uploaded files.
Why VidNavigator?
- Multi-platform transcription — YouTube, Instagram Reels & carousel posts, TikTok, X/Twitter, Vimeo, Facebook, Dailymotion, Loom, and more.
- Async TikTok profile scraping and keyword search — collect public TikTok videos in the background, then read results via cursor pagination or a signed
download_url. - Instagram carousel support — select a specific video by index, or transcribe every video in a carousel post with one call.
- AI-powered analysis — get summaries, people mentioned, places, key subjects, and direct answers to questions about any video or audio.
- Structured data extraction — define a JSON schema and receive typed, structured fields extracted from any transcript (powered by LLMs).
- AI search and reranking — YouTube search and file search with AI ranking/reranking and rich result metadata.
- File management with namespaces — upload audio/video files, organize them into namespaces, and scope searches and analysis by folder.
- Full TypeScript support — rich types, autocompletion, and compile-time safety out of the box.
- Comprehensive error handling — dedicated error classes for every API error code (
AuthenticationError,RateLimitExceededError,GeoRestrictedError, and more). - Lightweight — only two runtime dependencies:
axiosandform-data.
Supported Platforms
| Platform | Transcript | Transcribe (speech-to-text) | Carousel |
|----------|:----------:|:---------------------------:|:--------:|
| YouTube | Yes | - | - |
| Instagram Reels | - | Yes | Yes |
| Instagram Posts | - | Yes | Yes (all_videos) |
| TikTok | Yes | Yes | - |
| X / Twitter | Yes | Yes | - |
| Vimeo | Yes | Yes | - |
| Facebook | Yes | Yes | - |
| Dailymotion | Yes | Yes | - |
| Loom | Yes | Yes | - |
| Uploaded files | Yes | Yes | - |
Transcript = fast caption/subtitle extraction. Transcribe = speech-to-text via AI models (works when captions are unavailable).
Installation
npm install vidnavigatoryarn add vidnavigatorpnpm add vidnavigatorRequirements: Node.js 16+ and a VidNavigator API key.
Quick Start
import { VidNavigatorClient } from 'vidnavigator';
const vn = new VidNavigatorClient({
apiKey: process.env.VIDNAVIGATOR_API_KEY!,
});Examples
1. Get a transcript (any platform)
There is a single getTranscript method that auto-detects the platform (YouTube, Vimeo, X/Twitter, TikTok, Facebook, Dailymotion, Loom, and more) from the URL.
const { video_info, transcript } = await vn.getTranscript({
video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
language: 'en',
});
console.log(video_info.title); // "Rick Astley - Never Gonna Give You Up"
console.log(video_info.channel); // "Rick Astley"
console.log(video_info.duration); // 212
// Timed segments by default
for (const seg of transcript.slice(0, 3)) {
console.log(`[${seg.start.toFixed(1)}s] ${seg.text}`);
}
// [0.0s] We're no strangers to love
// [3.4s] You know the rules and so do I
// [6.8s] A full commitment's what I'm thinking ofPass transcript_text: true to get the full transcript as a single plain-text string instead of segments.
Deprecated:
getYouTubeTranscript()still works but is deprecated — it now forwards togetTranscript()and emits a deprecation warning. UsegetTranscript()instead.
2. Instagram Reel / TikTok / X / Vimeo
For most non-Instagram platforms, you can use either getTranscript (fast, caption-based) or transcribeVideo (speech-to-text). Note: Instagram only supports transcribeVideo.
// Instagram Reel (speech-to-text only)
const { video_info, transcript } = await vn.transcribeVideo({
video_url: 'https://www.instagram.com/reel/C86ZvEaqRmo/',
});
console.log(video_info.title);
console.log(transcript[0].text);
// TikTok (can use getTranscript or transcribeVideo)
const tiktok = await vn.getTranscript({
video_url: 'https://www.tiktok.com/@user/video/1234567890',
});
// X / Twitter
const tweet = await vn.getTranscript({
video_url: 'https://twitter.com/user/status/1234567890',
});3. TikTok profile scraping (async)
TikTok profile scraping is asynchronous. First submit the profile URL, then poll the task until task_status is no longer processing. Once complete, you can either page through results with getTikTokProfileScrape() or fetch the full JSON from download_url.
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const submitted = await vn.submitTikTokProfileScrape({
profile_url: 'https://www.tiktok.com/@tiktok',
max_posts: 250,
after_datetime: '2024-01-01',
});
let task = await vn.getTikTokProfileScrape(submitted.task_id, { limit: 50 });
while (task.task_status === 'processing') {
await sleep(5000);
task = await vn.getTikTokProfileScrape(submitted.task_id, { limit: 50 });
}
if (task.task_status === 'failed') {
throw new Error(task.error_message || 'TikTok profile scrape failed');
}
console.log(`Matched ${task.stats?.videos_matched ?? task.pagination?.total_items ?? 0} videos`);Use cursor pagination when you want to process videos page by page:
const videos = [];
let cursor: string | undefined;
do {
const page = await vn.getTikTokProfileScrape(submitted.task_id, {
limit: 100,
cursor,
});
videos.push(...page.videos);
cursor = page.pagination?.next_cursor ?? undefined;
} while (cursor);
console.log(`Loaded ${videos.length} TikTok videos`);Use download_url when you want the entire profile result in one request. The URL is short-lived and points directly to the generated JSON file, so it can be fetched with any HTTP client.
if (!task.download_url) {
throw new Error('No download URL is available for this scrape');
}
const response = await fetch(task.download_url);
const fullProfile = await response.json();
const videos = fullProfile.videos ?? [];
console.log(`Downloaded ${videos.length} videos for`, fullProfile.profile_url);After you have the final video list, loop through each TikTok video URL and use the normal transcript or extraction APIs:
for (const video of videos) {
if (!video.url) continue;
console.log(video.published_at?.toISOString()); // Date parsed from API `published_at`
const { transcript } = await vn.getTranscript({
video_url: video.url,
transcript_text: true,
fallback_to_metadata: true,
});
console.log(video.title, transcript);
}for (const video of videos) {
if (!video.url) continue;
const { data } = await vn.extractVideoData({
video_url: video.url,
schema: {
hook: { type: 'String', description: 'The opening hook or premise' },
products: { type: 'Array', description: 'Products or brands mentioned' },
sentiment: {
type: 'Enum',
description: 'Overall sentiment',
enum: ['positive', 'negative', 'neutral', 'mixed'],
},
},
what_to_extract: 'Extract creator messaging and product mentions.',
transcribe: true,
});
console.log(video.url, data);
}For large profiles, process videos sequentially or with a small concurrency limit so you do not exhaust credits or hit rate limits.
4. TikTok keyword search (async)
TikTok keyword search follows the same async pattern as profile scraping: submit a search, then poll by task_id until results are ready.
const submitted = await vn.submitTikTokSearch({
query: 'ai tools',
max_results: 100,
parallel_search_slices: 2,
after_datetime: '2024-01-01',
});
let task = await vn.getTikTokSearch(submitted.task_id, { limit: 50 });
while (task.task_status === 'processing') {
await sleep(5000);
task = await vn.getTikTokSearch(submitted.task_id, { limit: 50 });
}
for (const result of task.results) {
console.log(result.published_at?.toISOString(), result.url, result.stats?.views);
}Use parallel_search_slices from 2 to 4 when you need broader collection than TikTok's natural single-chain cap. Billing scales with the number of slices, so keep it at 1 unless you need the wider result set.
5. Instagram carousel posts (multiple videos)
Instagram carousel posts can contain multiple videos. You can select a specific video by index, or transcribe them all at once:
// Transcribe the 2nd video in a carousel
const single = await vn.transcribeVideo({
video_url: 'https://www.instagram.com/p/ABC123/?img_index=2',
});
console.log(single.video_info.title);
console.log(single.video_info.carousel_info);
// { total_items: 5, video_count: 3, image_count: 2, selected_index: 2 }
// Transcribe ALL videos in a carousel at once
const all = await vn.transcribeVideo({
video_url: 'https://www.instagram.com/p/ABC123/',
all_videos: true,
});
if ('carousel_info' in all) {
console.log(`${all.carousel_info.transcribed_count} of ${all.carousel_info.video_count} videos transcribed`);
for (const video of all.videos) {
if (video.status === 'success') {
console.log(`Video #${video.index}: ${video.video_info.title}`);
console.log(` Transcript: ${video.transcript[0]?.text}...`);
} else {
console.log(`Video #${video.index}: failed — ${video.message}`);
}
}
}6. Upload and analyze a local file
Upload audio or video files for transcription, analysis, and search. Supported formats: mp4, webm, mov, avi, wmv, flv, mkv, m4a, mp3, mpeg, mpga, wav.
const upload = await vn.uploadFile({
filePath: './meeting-recording.mp4',
wait_for_completion: true,
namespace_ids: ['ns_meetings'], // optional: file goes into a namespace
});
console.log(upload.file_id); // "64a1b2c3d4e5f6789..."
console.log(upload.file_name); // "meeting-recording.mp4"
console.log(upload.file_info.namespace_ids); // ["ns_meetings"]
// Analyze with a question
const { transcript_analysis } = await vn.analyzeFile({
file_id: upload.file_info.id,
query: 'What action items were discussed?',
});
console.log(transcript_analysis.summary);
// "The meeting covered Q3 targets, hiring plans, and a product launch timeline..."
console.log(transcript_analysis.people);
// [{ name: "Sarah", context: "VP of Engineering, presented hiring plan" }, ...]
console.log(transcript_analysis.key_subjects);
// [{ name: "Q3 targets", description: "Revenue goals for...", importance: "high" }, ...]
console.log(transcript_analysis.query_answer?.answer);
// "Three action items were discussed: 1) Finalize the hiring..."7. Extract structured data
Define a schema and get back clean, structured data extracted from any video or file transcript. Powered by LLMs with per-call usage tracking (see Per-call usage).
const { data, usage } = await vn.extractVideoData({
video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
schema: {
topic: { type: 'String', description: 'Main topic of the video' },
language: { type: 'String', description: 'Primary spoken language (ISO 639-1)' },
tone: { type: 'Enum', description: 'Overall tone',
enum: ['positive', 'negative', 'neutral', 'mixed'] },
key_quotes: { type: 'Array', description: 'Top 3 memorable quotes' },
},
what_to_extract: 'Determine the topic, language, tone, and notable quotes.',
include_usage: true,
});
console.log(data);
// {
// topic: "A classic pop love song",
// language: "en",
// tone: "positive",
// key_quotes: ["Never gonna give you up", "Never gonna let you down", ...]
// }
// LLM token counts live under the analysis_request charge:
console.log(usage?.analysis_tokens?.total_tokens); // 847
console.log(usage?.total_credits); // 2Also works on uploaded files:
const { data } = await vn.extractFileData({
file_id: 'your-file-id',
schema: {
action_items: { type: 'Array', description: 'List of action items from the meeting' },
next_meeting: { type: 'String', description: 'When is the next meeting scheduled?' },
sentiment: { type: 'Enum', description: 'Overall meeting mood',
enum: ['productive', 'tense', 'casual', 'urgent'] },
},
});For larger schemas, pass a JSON or YAML schema file with multipart form-data:
const { data } = await vn.extractVideoData({
video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
schemaFilePath: './schemas/video-extraction.yaml',
include_usage: true,
});Supported schema types: String, Number, Boolean, Integer, Object, Array, Enum
8. Semantic search
Search YouTube videos and uploaded files with AI-powered ranking/reranking.
// Search YouTube videos
const videoResults = await vn.searchYouTube({
query: 'how to deploy a Node.js app',
focus: 'relevance', // 'relevance' (default) | 'popularity' | 'brevity'
max_results: 5, // caps how many candidates are analysed (and the cost)
include_usage: true, // attach a per-call usage block
});
console.log(`Found ${videoResults.total_found} results`);
for (const r of videoResults.results) {
console.log(`${r.title} — score: ${r.relevance_score}`);
console.log(` ${r.transcript_summary}`);
}
// If credits run out mid-search, a partial result is returned instead of throwing:
if (videoResults.status === 'partial') {
console.log('Partial results:', videoResults.error_code); // "insufficient_credits_video_search"
}
// Search uploaded files — optionally scoped to a namespace
const fileResults = await vn.searchFiles({
query: 'quarterly revenue discussion',
namespace_ids: ['ns_finance'],
});
for (const r of fileResults.results) {
console.log(`${r.name} (score: ${r.relevance_score})`);
console.log(` Answer: ${r.query_answer}`);
console.log(` Namespaces: ${r.namespaces?.map(n => n.name).join(', ')}`);
}Deprecated:
searchVideos()still works but is deprecated — it now forwards tosearchYouTube()(the/search/videoendpoint moved to/youtube/search) and emits a deprecation warning. UsesearchYouTube()instead.
9. Organize files with namespaces
// Create a namespace
const ns = await vn.createNamespace({ name: 'Client Calls' });
// Assign a file to namespaces
const updated = await vn.updateFileNamespaces(fileId, {
namespace_ids: [ns.id],
});
console.log(updated.namespaces);
// [{ id: "...", name: "Client Calls" }]
// List files filtered by namespace
const files = await vn.getFiles({ namespace_id: ns.id });
// List all namespaces
const all = await vn.getNamespaces();10. Usage and credits
const usage = await vn.getUsage();
console.log(`Credits remaining: ${usage.credits.monthly_remaining}`);
console.log(`Video transcripts used: ${usage.usage.videoTranscripts.used}`);
console.log(`YouTube transcripts used: ${usage.usage.youtubeTranscripts.used}`);
console.log(`Storage: ${usage.storage.used_formatted} / ${usage.storage.limit_formatted}`);
console.log(`Channels indexed: ${usage.channelsIndexed.used} / ${usage.channelsIndexed.limit}`);Per-call usage
Pass include_usage: true to most endpoints (getTranscript, transcribeVideo, analyzeVideo, analyzeFile, extractVideoData, extractFileData, searchYouTube, searchFiles) to receive a usage block describing exactly what the request cost. For the TikTok pollers (getTikTokProfileScrape, getTikTokSearch), pass include_usage: true in the options object — usage is populated once task_status === 'completed'.
The usage field is a UsageBlock:
const { usage } = await vn.getTranscript({
video_url: 'https://youtube.com/watch?v=dQw4w9WgXcQ',
include_usage: true,
});
if (usage) {
console.log(usage.total_credits); // net credits deducted by this request
console.log(usage.credits_remaining_after); // balance after the request (when provided)
// Every meter that fired, consolidated to one entry per service_type:
for (const charge of usage.charges) {
console.log(charge.service_type, charge.quantity, charge.credits, charge.waived);
}
// Convenience accessors:
const residential = usage.charge_for('residential_request'); // matching charge entry or undefined
const tokens = usage.analysis_tokens; // { prompt_tokens, completion_tokens, total_tokens }
console.log(tokens?.total_tokens);
}service_type is one of: standard_request, residential_request, transcription_hour, analysis_request, search_request, scene_analysis_hour. LLM token counts (for /extract/*, /analyze/*, and /youtube/search) are nested inside the analysis_request charge — read them via the analysis_tokens accessor rather than expecting flat total_tokens. When a charge is waived via a cache-hit sponsorship, the entry carries waived: true + credits_saved, and the block carries a top-level waived.credits_saved.
API Reference
All methods return a Promise. Responses are automatically parsed into typed model classes.
Transcripts
| Method | Description |
|--------|-------------|
| getTranscript(payload) | Get a transcript for any supported video; auto-detects the platform from the URL (note: Instagram uses transcribeVideo) |
| getYouTubeTranscript(payload) | Deprecated — forwards to getTranscript() |
| transcribeVideo(payload) | Speech-to-text transcription via AI models; supports Instagram carousel with all_videos |
Common options: video_url, language, metadata_only, fallback_to_metadata, transcript_text, include_usage
TikTok
| Method | Description |
|--------|-------------|
| submitTikTokProfileScrape(payload) | Start an async public TikTok profile scrape. Returns a task_id immediately |
| getTikTokProfileScrape(task_id, query?) | Poll task status and retrieve a page of videos. Options: cursor, limit, include_usage |
| submitTikTokSearch(payload) | Start an async TikTok keyword search. Returns a task_id immediately |
| getTikTokSearch(task_id, query?) | Poll search status and retrieve a page of results. Options: cursor, limit, include_usage |
Completed profile tasks include videos; completed search tasks include results. Both include pagination, optional stats, and an optional short-lived download_url for retrieving the whole result as JSON. Datetime filters use after_datetime and before_datetime, each accepting either YYYY-MM-DD or full ISO datetime strings with timezone; TikTok published_at values are parsed into JavaScript Date objects, and numeric counters such as views and likes are normalized to integers.
Files
| Method | Description |
|--------|-------------|
| getFiles(query?) | List uploaded files (paginated). Filter by status or namespace_id |
| getFile(file_id, query?) | Get file info and transcript. Pass transcript_text: true for plain text |
| uploadFile(options) | Upload audio/video. Options: filePath, wait_for_completion, namespace_ids |
| deleteFile(file_id) | Delete a file |
| getFileUrl(file_id) | Get a signed download URL |
| retryFileProcessing(file_id) | Retry a failed processing job |
| cancelFileUpload(file_id) | Cancel an in-progress upload |
Supported formats: mp4, webm, mov, avi, wmv, flv, mkv, m4a, mp3, mpeg, mpga, wav
Namespaces
| Method | Description |
|--------|-------------|
| getNamespaces() | List all namespaces |
| createNamespace({ name }) | Create a new namespace |
| updateNamespace(id, { name }) | Rename a namespace |
| deleteNamespace(id) | Delete a namespace |
| updateFileNamespaces(file_id, { namespace_ids }) | Assign a file to namespaces. Returns updated namespace_ids and namespaces |
Analysis
| Method | Description |
|--------|-------------|
| analyzeVideo(payload) | Analyze an online video with an optional natural language query |
| analyzeFile(payload) | Analyze an uploaded file with an optional natural language query |
| getTweetStatement(payload) | Extract a structured claim analysis from an X/Twitter tweet ID |
Returns transcript_analysis containing:
summary— content overviewpeople— people mentioned (name + context)places— locations referencedkey_subjects— important topics (name, description, importance)query_answer— direct answer to your query (when provided) withanswer,best_segment_index, andrelevant_segments
Extraction
| Method | Description |
|--------|-------------|
| extractVideoData(payload) | Extract structured data from an online video transcript |
| extractFileData(payload) | Extract structured data from an uploaded file transcript |
Options: schema (required for JSON requests), schemaFilePath (JSON/YAML schema file via multipart form-data), what_to_extract (optional guidance), include_usage (attach a per-call UsageBlock; read LLM tokens via usage.analysis_tokens)
Schema field types: String, Number, Boolean, Integer, Object, Array, Enum
Search
| Method | Description |
|--------|-------------|
| searchYouTube(payload) | Search YouTube videos with AI ranking/reranking |
| searchVideos(payload) | Deprecated — forwards to searchYouTube() |
| searchFiles(payload) | Semantic search across uploaded files, with optional namespace_ids scope |
searchYouTube options: query (required), use_enhanced_search (default true), start_year, end_year, focus ('relevance' (default) | 'popularity' | 'brevity'), duration, max_results (caps candidates analysed & cost; defaults to your plan ceiling), include_usage.
Results include relevance_score, transcript_summary, people, places, key_subjects, query_answer, timestamps, and relevant_text. If credits run out mid-search the API returns a partial result: searchYouTube surfaces it as { status: 'partial', error_code: 'insufficient_credits_video_search', results, usage? } instead of throwing.
System
| Method | Description |
|--------|-------------|
| getUsage() | Credits, activity counters, storage, channels indexed, subscription info |
| healthCheck() | API health status (no auth required) |
Error Handling
Every API error is mapped to a specific exception class, all extending VidNavigatorError:
import {
VidNavigatorClient,
AuthenticationError,
NotFoundError,
RateLimitExceededError,
} from 'vidnavigator';
try {
await vn.getFile('nonexistent-id');
} catch (error) {
if (error instanceof NotFoundError) {
console.log(error.status_code); // 404
console.log(error.error_message); // "File not found"
} else if (error instanceof RateLimitExceededError) {
console.log('Slow down! Rate limited.');
}
}| Error Class | HTTP | When |
|-------------|:----:|------|
| BadRequestError | 400 | Invalid parameters |
| AuthenticationError | 401 | Invalid or missing API key |
| PaymentRequiredError | 402 | Credit limit reached |
| AccessDeniedError | 403 | Insufficient permissions |
| NotFoundError | 404 | Resource does not exist |
| StorageQuotaExceededError | 413 | Storage quota exceeded |
| RateLimitExceededError | 429 | Too many requests |
| GeoRestrictedError | 451 | Content unavailable in your region |
| ServerError | 5xx | Unexpected server error |
| SystemOverloadError | 503 | Temporary overload (has retry_after_seconds) |
TypeScript Models
All API responses are parsed into typed classes with static fromJSON() constructors:
| Class | Description |
|-------|-------------|
| VideoInfo | Video metadata: title, channel, duration, views, likes, published date, keywords, carousel info |
| FileInfo | Uploaded file: name, size, type, duration, status, namespace_ids, namespaces |
| TranscriptSegment | Timed segment: text, start, end (seconds) |
| AnalysisResult | AI analysis: summary, people, places, key subjects, query answer |
| Namespace | Full namespace: id, name, created/updated timestamps |
| NamespaceRef | Lightweight { id, name } reference embedded in file and search responses |
| VideoSearchResult | Extends VideoInfo with relevance_score, transcript_summary, search metadata |
| FileSearchResult | Extends FileInfo with relevance_score, query_answer, timestamps, signed URL |
| UsageData | Account-level usage from getUsage(): credits info, per-service activity counts, storage metrics, subscription details |
| UsageBlock | Per-call usage returned when include_usage: true: charges[], total_credits, credits_remaining_after, waived, plus charge_for(service_type) and analysis_tokens accessors |
| UsageCharge | A single consolidated meter charge: service_type, quantity, credits, waived, credits_saved, and (on analysis_request) tokens |
| ExtractionTokenUsage | Legacy flat token usage (prompt_tokens, completion_tokens, total_tokens) — extraction now returns a UsageBlock; prefer usage.analysis_tokens |
| CarouselInfo | Carousel summary: total items, video/image count, transcribed count, total duration |
| CarouselVideoResult | Per-video result in a carousel: index, status, video info, transcript |
| TikTokProfileScrapeSubmission | Async scrape submission: task_id, status, profile URL, expiry |
| TikTokProfileTask | TikTok profile scrape task with status, profile metadata, videos, pagination, download_url |
| TikTokVideo | Public TikTok video metadata returned by profile scraping, including published_at as a Date and integer counters |
| TikTokSearchSubmission | Async TikTok keyword search submission: task_id, query, slice count, expiry |
| TikTokSearchTask | TikTok keyword search task with status, results, pagination, download_url |
| TikTokSearchResult | Normalized TikTok keyword result, including published_at as a Date and integer counters |
| TweetStatement | Structured X/Twitter claim analysis, topics, entities, tone, intent, and source text |
