curl --request POST \
--url https://api.vidnavigator.com/v1/tweet/statement/async \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"tweet_id": "1234567890123456789",
"webhook_url": "https://example.com/hooks/vidnavigator"
}
'import requests
url = "https://api.vidnavigator.com/v1/tweet/statement/async"
payload = {
"tweet_id": "1234567890123456789",
"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({
tweet_id: '1234567890123456789',
webhook_url: 'https://example.com/hooks/vidnavigator'
})
};
fetch('https://api.vidnavigator.com/v1/tweet/statement/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/tweet/statement/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([
'tweet_id' => '1234567890123456789',
'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/tweet/statement/async"
payload := strings.NewReader("{\n \"tweet_id\": \"1234567890123456789\",\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/tweet/statement/async")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"tweet_id\": \"1234567890123456789\",\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vidnavigator.com/v1/tweet/statement/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 \"tweet_id\": \"1234567890123456789\",\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": "<string>",
"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>"
}Tweet Claim Analysis (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.
curl --request POST \
--url https://api.vidnavigator.com/v1/tweet/statement/async \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"tweet_id": "1234567890123456789",
"webhook_url": "https://example.com/hooks/vidnavigator"
}
'import requests
url = "https://api.vidnavigator.com/v1/tweet/statement/async"
payload = {
"tweet_id": "1234567890123456789",
"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({
tweet_id: '1234567890123456789',
webhook_url: 'https://example.com/hooks/vidnavigator'
})
};
fetch('https://api.vidnavigator.com/v1/tweet/statement/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/tweet/statement/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([
'tweet_id' => '1234567890123456789',
'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/tweet/statement/async"
payload := strings.NewReader("{\n \"tweet_id\": \"1234567890123456789\",\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/tweet/statement/async")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"tweet_id\": \"1234567890123456789\",\n \"webhook_url\": \"https://example.com/hooks/vidnavigator\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.vidnavigator.com/v1/tweet/statement/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 \"tweet_id\": \"1234567890123456789\",\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": "<string>",
"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 /tweet/statement 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
Submit the job
POST /tweet/statement/async with the same body as POST /tweet/statement, plus an optional webhook_url. You get 202 Accepted with a task_id and a check_status_url.Wait for the result
GET /tweet/statement/{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.Billing
Billed exactly likePOST /tweet/statement — 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; pass "" to opt this job out of the default. Must be a public https URL. See Webhooks. |
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"
}'
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"])
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)
{
"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
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. |
completed, data.result is identical to the data block of the synchronous response (final_statement, detailed_analysis, topics, entities, classification axes and media summaries).
{
"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
}
}
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
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
The X/Twitter tweet ID
"1234567890123456789"
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"

