curl --request POST \
--url https://api.vidnavigator.com/v1/extract/video/async \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"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"
}
},
"sentiment": {
"type": "Enum",
"description": "Overall sentiment of the video",
"enum": [
"positive",
"negative",
"neutral"
]
},
"key_takeaway": {
"type": "String",
"description": "The single most important takeaway"
}
},
"what_to_extract": "<string>",
"transcribe": true,
"webhook_url": "https://example.com/hooks/vidnavigator"
}
'import requests
url = "https://api.vidnavigator.com/v1/extract/video/async"
payload = {
"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"
}
},
"sentiment": {
"type": "Enum",
"description": "Overall sentiment of the video",
"enum": ["positive", "negative", "neutral"]
},
"key_takeaway": {
"type": "String",
"description": "The single most important takeaway"
}
},
"what_to_extract": "<string>",
"transcribe": True,
"webhook_url": "https://example.com/hooks/vidnavigator"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
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'}
},
sentiment: {
type: 'Enum',
description: 'Overall sentiment of the video',
enum: ['positive', 'negative', 'neutral']
},
key_takeaway: {type: 'String', description: 'The single most important takeaway'}
},
what_to_extract: '<string>',
transcribe: true,
webhook_url: 'https://example.com/hooks/vidnavigator'
})
};
fetch('https://api.vidnavigator.com/v1/extract/video/async', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.vidnavigator.com/v1/extract/video/async",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'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'
]
],
'sentiment' => [
'type' => 'Enum',
'description' => 'Overall sentiment of the video',
'enum' => [
'positive',
'negative',
'neutral'
]
],
'key_takeaway' => [
'type' => 'String',
'description' => 'The single most important takeaway'
]
],
'what_to_extract' => '<string>',
'transcribe' => true,
'webhook_url' => 'https://example.com/hooks/vidnavigator'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.vidnavigator.com/v1/extract/video/async"
payload := strings.NewReader("{\n \"video_url\": \"https://www.tiktok.com/@user/video/1234567890\",\n \"schema\": {\n \"main_topics\": {\n \"type\": \"Array\",\n \"description\": \"List of main topics discussed\",\n \"items\": {\n \"type\": \"String\",\n \"description\": \"A topic\"\n }\n },\n \"sentiment\": {\n \"type\": \"Enum\",\n \"description\": \"Overall sentiment of the video\",\n \"enum\": [\n \"positive\",\n \"negative\",\n \"neutral\"\n ]\n },\n \"key_takeaway\": {\n \"type\": \"String\",\n \"description\": \"The single most important takeaway\"\n }\n },\n \"what_to_extract\": \"<string>\",\n \"transcribe\": true,\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.vidnavigator.com/v1/extract/video/async")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"video_url\": \"https://www.tiktok.com/@user/video/1234567890\",\n \"schema\": {\n \"main_topics\": {\n \"type\": \"Array\",\n \"description\": \"List of main topics discussed\",\n \"items\": {\n \"type\": \"String\",\n \"description\": \"A topic\"\n }\n },\n \"sentiment\": {\n \"type\": \"Enum\",\n \"description\": \"Overall sentiment of the video\",\n \"enum\": [\n \"positive\",\n \"negative\",\n \"neutral\"\n ]\n },\n \"key_takeaway\": {\n \"type\": \"String\",\n \"description\": \"The single most important takeaway\"\n }\n },\n \"what_to_extract\": \"<string>\",\n \"transcribe\": true,\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vidnavigator.com/v1/extract/video/async")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"video_url\": \"https://www.tiktok.com/@user/video/1234567890\",\n \"schema\": {\n \"main_topics\": {\n \"type\": \"Array\",\n \"description\": \"List of main topics discussed\",\n \"items\": {\n \"type\": \"String\",\n \"description\": \"A topic\"\n }\n },\n \"sentiment\": {\n \"type\": \"Enum\",\n \"description\": \"Overall sentiment of the video\",\n \"enum\": [\n \"positive\",\n \"negative\",\n \"neutral\"\n ]\n },\n \"key_takeaway\": {\n \"type\": \"String\",\n \"description\": \"The single most important takeaway\"\n }\n },\n \"what_to_extract\": \"<string>\",\n \"transcribe\": true,\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"task_status": "processing",
"job_type": "transcribe",
"expires_at": "2023-11-07T05:31:56Z",
"check_status_url": "/v1/transcribe/550e8400-e29b-41d4-a716-446655440000",
"webhook_url": "<string>",
"message": "<string>",
"docs_url": "<string>"
}
}{
"status": "error",
"error": "missing_parameter",
"message": "<string>"
}{
"status": "error",
"error": "limit_exceeded",
"error_code": "limit_exceeded",
"message": "<string>"
}{
"status": "error",
"error": "too_many_active_jobs",
"message": "<string>"
}{
"status": "error",
"error": "metadata_fetch_failed",
"message": "<string>"
}Extract Data from 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.
curl --request POST \
--url https://api.vidnavigator.com/v1/extract/video/async \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"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"
}
},
"sentiment": {
"type": "Enum",
"description": "Overall sentiment of the video",
"enum": [
"positive",
"negative",
"neutral"
]
},
"key_takeaway": {
"type": "String",
"description": "The single most important takeaway"
}
},
"what_to_extract": "<string>",
"transcribe": true,
"webhook_url": "https://example.com/hooks/vidnavigator"
}
'import requests
url = "https://api.vidnavigator.com/v1/extract/video/async"
payload = {
"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"
}
},
"sentiment": {
"type": "Enum",
"description": "Overall sentiment of the video",
"enum": ["positive", "negative", "neutral"]
},
"key_takeaway": {
"type": "String",
"description": "The single most important takeaway"
}
},
"what_to_extract": "<string>",
"transcribe": True,
"webhook_url": "https://example.com/hooks/vidnavigator"
}
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
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'}
},
sentiment: {
type: 'Enum',
description: 'Overall sentiment of the video',
enum: ['positive', 'negative', 'neutral']
},
key_takeaway: {type: 'String', description: 'The single most important takeaway'}
},
what_to_extract: '<string>',
transcribe: true,
webhook_url: 'https://example.com/hooks/vidnavigator'
})
};
fetch('https://api.vidnavigator.com/v1/extract/video/async', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.vidnavigator.com/v1/extract/video/async",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'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'
]
],
'sentiment' => [
'type' => 'Enum',
'description' => 'Overall sentiment of the video',
'enum' => [
'positive',
'negative',
'neutral'
]
],
'key_takeaway' => [
'type' => 'String',
'description' => 'The single most important takeaway'
]
],
'what_to_extract' => '<string>',
'transcribe' => true,
'webhook_url' => 'https://example.com/hooks/vidnavigator'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.vidnavigator.com/v1/extract/video/async"
payload := strings.NewReader("{\n \"video_url\": \"https://www.tiktok.com/@user/video/1234567890\",\n \"schema\": {\n \"main_topics\": {\n \"type\": \"Array\",\n \"description\": \"List of main topics discussed\",\n \"items\": {\n \"type\": \"String\",\n \"description\": \"A topic\"\n }\n },\n \"sentiment\": {\n \"type\": \"Enum\",\n \"description\": \"Overall sentiment of the video\",\n \"enum\": [\n \"positive\",\n \"negative\",\n \"neutral\"\n ]\n },\n \"key_takeaway\": {\n \"type\": \"String\",\n \"description\": \"The single most important takeaway\"\n }\n },\n \"what_to_extract\": \"<string>\",\n \"transcribe\": true,\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.vidnavigator.com/v1/extract/video/async")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"video_url\": \"https://www.tiktok.com/@user/video/1234567890\",\n \"schema\": {\n \"main_topics\": {\n \"type\": \"Array\",\n \"description\": \"List of main topics discussed\",\n \"items\": {\n \"type\": \"String\",\n \"description\": \"A topic\"\n }\n },\n \"sentiment\": {\n \"type\": \"Enum\",\n \"description\": \"Overall sentiment of the video\",\n \"enum\": [\n \"positive\",\n \"negative\",\n \"neutral\"\n ]\n },\n \"key_takeaway\": {\n \"type\": \"String\",\n \"description\": \"The single most important takeaway\"\n }\n },\n \"what_to_extract\": \"<string>\",\n \"transcribe\": true,\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vidnavigator.com/v1/extract/video/async")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"video_url\": \"https://www.tiktok.com/@user/video/1234567890\",\n \"schema\": {\n \"main_topics\": {\n \"type\": \"Array\",\n \"description\": \"List of main topics discussed\",\n \"items\": {\n \"type\": \"String\",\n \"description\": \"A topic\"\n }\n },\n \"sentiment\": {\n \"type\": \"Enum\",\n \"description\": \"Overall sentiment of the video\",\n \"enum\": [\n \"positive\",\n \"negative\",\n \"neutral\"\n ]\n },\n \"key_takeaway\": {\n \"type\": \"String\",\n \"description\": \"The single most important takeaway\"\n }\n },\n \"what_to_extract\": \"<string>\",\n \"transcribe\": true,\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}"
response = http.request(request)
puts response.read_body{
"status": "success",
"data": {
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"task_status": "processing",
"job_type": "transcribe",
"expires_at": "2023-11-07T05:31:56Z",
"check_status_url": "/v1/transcribe/550e8400-e29b-41d4-a716-446655440000",
"webhook_url": "<string>",
"message": "<string>",
"docs_url": "<string>"
}
}{
"status": "error",
"error": "missing_parameter",
"message": "<string>"
}{
"status": "error",
"error": "limit_exceeded",
"error_code": "limit_exceeded",
"message": "<string>"
}{
"status": "error",
"error": "too_many_active_jobs",
"message": "<string>"
}{
"status": "error",
"error": "metadata_fetch_failed",
"message": "<string>"
}task_id immediately, then collect the result by polling or through a webhook.
POST /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
Submit the job
POST /extract/video/async with the same body as POST /extract/video, plus an optional webhook_url. You get 202 Accepted with a task_id and a check_status_url.Wait for the result
GET /extract/video/{task_id} every few seconds while task_status is processing — polling is free — or receive a webhook when the job finishes.Read the result
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.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 likePOST /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; pass "" to opt this job out of the default. Must be a public https URL. See Webhooks. |
include_usage is not accepted on the submit request — pass it on the poll request instead.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"
}'
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"])
else:
print("Failed:", job["error"]["error"], job["error"]["message"])
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' ? job.result : job.error);
Response (202 Accepted)
{
"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
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. |
completed, data.result is identical to the data block of a successful synchronous POST /extract/video response.
{
"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"
},
"error": null
}
}
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
Passwebhook_url on the submit request, or configure a default endpoint in Studio → API, to be called back when the job finishes. See Webhooks for the payload and signature verification, and Async Jobs for the full workflow.Authorizations
API key authentication. Include your VidNavigator API key in the X-API-Key header.
Body
URL of the video to extract data from
"https://www.tiktok.com/@user/video/1234567890"
Custom extraction schema defining the fields to extract. Max 10 root-level fields, max 3 nesting levels. Each field must have type and description.
Show child attributes
Show child attributes
{ "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" } }
Optional guidance for what to extract from the transcript
When true, automatically transcribes the video audio if no platform transcript is available.
Where to POST the result when the job finishes. Overrides the account-level default configured in Studio → API. Pass an empty string to opt this job out of that default. Must be a publicly reachable https URL — private, loopback and link-local hosts are rejected. See https://docs.vidnavigator.com/guides/webhooks
"https://example.com/hooks/vidnavigator"

