REST over HTTPS, JSON in and out. One key and one data shape for eight platforms. Start with the quickstart, then pick an endpoint; every card shows its cost, parameters and a real response.
Quickstart
Sign up and you get an API key with 100 free credits instantly. Set two environment variables, then make your first call. SW is the API base URL, https://api.vanefeed.com.
export SW="https://api.vanefeed.com"
export SW_KEY="sk_live_your_key"
curl "$SW/v1/youtube/profile?handle=@TED" \
-H "Authorization: Bearer $SW_KEY"
# swap the platform and endpoint for anything else:
# /v1/x/search?q=(artemis OR moon) launch&limit=100
# pip install requests
import os, requests
SW, KEY = os.environ["SW"], os.environ["SW_KEY"]
r = requests.get(f"{SW}/v1/youtube/posts",
params={"handle": "@TED", "limit": 20},
headers={"Authorization": f"Bearer {KEY}"})
for m in r.json()["data"]:
print(m["platform"], m["author_handle"], m["text"][:80])
Authorization: Bearer sk_live_…
# or
X-API-Key: sk_live_…
Your first key arrives when you sign up. Create up to 5 keys in the dashboard; they share one credit balance.
To see a key again, open the dashboard's API keys and press Show (your password is required). Store keys in a secret manager.
If a key may have leaked, press Roll: you get a new key and the old one stops working at once.
Each key has scopes. A call without the needed scope returns 403 forbidden.
Scope
Grants
mentions:read
On-demand single posts, comments and search; mention search, single mention, trends
accounts:read
On-demand profiles, posts, followers and following; account lookup and posts
webhooks:write
Create webhooks
stream:read
Live SSE stream
Credits & rate limits
Each endpoint's cost is shown on its card below. List endpoints (posts, comments, search, followers, following) cost per 20 results returned, up to 1,000 per call. Set how many you want with limit: posts, comments and search return 100 by default; followers and following return 20. Some platforms have a lower ceiling per call (Instagram posts 33, Instagram comments 50, Instagram search 100, news 100, Facebook comments 200, Reddit search 250); a larger limit is trimmed to it. Example: limit=100 costs at most 5 blocks of 20. Platforms hand out results about 20 at a time, so 100 take several seconds. For the fastest first results, ask for limit=20, show them, then fetch the rest with next_cursor: we start loading the next page while you read, and the total price is the same. Before a call runs we hold the most it could cost; when it finishes you pay only for what came back, and failed or cached calls cost nothing. Parallel calls can never spend the same credits twice.
If your balance can't cover a call, you get 402 insufficient_credits with affordable_limit: the largest limit your balance covers, so your code can retry smaller. Two token-bucket rate limits apply: one per key, then one per plan.
Header
Meaning
Status
X-RateLimit-Limit
Requests per minute for this key
Live
X-RateLimit-Remaining
Requests left in the current minute
Live
Retry-After
Seconds to wait, on every 429
Live
X-Credits-Cost
Credits this call used. Always 0 for failed or rejected calls.
Live
X-Credits-Remaining
Credits left on this key after the call
Live
X-Credits-Low
true when your prepaid balance is under 100 credits, so your code can warn you before calls start failing with 402
Live
X-Cache
HIT when an on-demand call was served from cache (free), MISS otherwise
Live
X-Request-Id
Unique id for the call. Send your own and we echo it back. Quote it to support.
Live
Plan
Requests / min
Free / trial
30
Starter
120
Growth
300
Custom
600–1,200+
Free price checks
Add dry_run=true to any endpoint to see what it would cost without calling it. Price checks are free and don't touch your balance. Use them before large batch jobs.
Every error uses the same shape. Read error in code and show message to people.
{ "error": "not_found", "message": "mention 12345 not found" }
HTTP
error
Meaning
400
bad_request
A parameter is wrong or the JSON body is malformed. The message names the problem.
401
unauthorized
Missing, invalid, revoked or expired key.
403
forbidden
The key doesn't have the scope this endpoint needs.
402
insufficient_credits
Your balance can't cover the most this call could cost. Free. The body has balance, cost and affordable_limit.
404
not_found
That account, post or channel doesn't exist or isn't public.
422
(detail)
The request body failed validation. See the detail array.
429
rate_limit_exceeded
Too many requests. Wait for the number of seconds in Retry-After.
429
quota_exceeded
The key's quota is spent. The body includes quota, used and retry_after.
500
internal_error
Something broke on our side. No stack traces, ever.
502
upstream_error
The platform sent something we couldn't read. Free; retry, and tell us if it persists.
503
platform_unavailable
The platform is limiting or blocking us right now. Free; the message says when to retry.
504
upstream_timeout
The platform took longer than 25 seconds. Free; retry shortly.
Sentiment
Add sentiment=true to any posts, post, comments, replies or search call (also TikTok's hashtag, top_search, trending, playlist and song_videos) and every result gets a sentiment object: label (positive, neutral or negative), score (how sure, 0 to 1) and model.
It costs 1 extra credit per 20 results (1 for a single post). It reads the whole post, not keywords, with a multilingual model trained on social media (Cardiff NLP's XLM-T): strongest in English, Arabic, Hindi, Spanish, French, German, Italian and Portuguese, and it also handles Urdu and Roman Urdu. Very short or slang-heavy posts are the hardest; score tells you how sure it is.
List endpoints return data, next_cursor and has_more. Pass next_cursor back as cursor until has_more is false. Cursors are opaque, so don't build them yourself.
cursor = None
while True:
params = {"q": "your-brand", "limit": 200}
if cursor:
params["cursor"] = cursor
page = requests.get(f"{SW}/v1/mentions/search", params=params,
headers={"Authorization": f"Bearer {KEY}"}).json()
handle(page["data"])
if not page["has_more"]:
break
cursor = page["next_cursor"]
On-demand
Live on nine platforms. These endpoints fetch fresh data at call time and return it in one shape for every platform: swap youtube in the path for x, bluesky, reddit, tiktok, telegram, instagram, facebook or web. Repeat requests within a few minutes come from cache and cost 0 credits (X-Cache: HIT).
Platform support
Which resources each platform serves, and the parameters it takes. Unknown parameters return a 400 that lists the allowed ones.
Platform
profile
posts
post
comments
search
Parameters
youtube
●
●
●
●
●
handle, id, q, tab, yt_params, hl, gl, limit, cursor
reddit
●
●
●
●
●
handle (subreddit), id (thread URL), q, subreddit, limit. Scores are null when Reddit only serves its RSS feed.
bluesky
●
●
●
●
●
handle (name.bsky.social, @handle, DID or bsky.app link), type (posts, replies, media, threads), id (bsky.app post link or at:// URI), q with sort (latest, top), author, lang, since, until, tag, domain; limit, cursor. Also /followers and /following.
telegram
●
●
●
—
●
handle (public channel), id (t.me/channel/123), q + handle (search inside a channel), limit, cursor
web
—
●
●
—
●
url (any news site, section or RSS/Atom feed), id (article URL: full text, author, date), q (news search), hl, gl, limit
x
●
●
●
●
●
handle, id (post id or URL), q, product (Latest, Top, Media), limit, cursor. Also /v1/x/followers, /following, /community (id = community id or link: name, members, rules, topic, creator; 1 credit), /community_posts (id, sort latest|top, limit, cursor; 2 credits per 20) and /transcript (id = a video post: its words as text plus timed lines. Captions when the video has them, 1 credit; otherwise its speech is transcribed, 1 credit + 1 per started minute. max_minutes (default 10, up to 60) caps the length and so the price; source = captions or speech forces one; lang e.g. en speeds it up)
tiktok
●
●
●
●
●
handle, id (tiktok.com/@user/video/… URL), q (video search), limit (profile videos and search up to 300), cursor. Profile videos and search take about 5–15 s. Video counts are null when TikTok only allows the embed view.
Also /v1/tiktok/replies (id = the video URL, comment_id from /comments; 2 credits per 20),
/followers and /following (handle, limit up to 1000; 2 per 20),
/transcript (id = video URL: TikTok's captions, 1 credit, or its speech transcribed, 1 + 1 per started minute; lang, source, max_minutes),
/live (handle: live now or not, viewers, likes, start time; 1 credit),
/region (handle: the country its recent videos were posted from; 1 credit),
/search_users and /top_search (q; Top search returns videos in data plus users; 3 per 20),
/suggestions (q: what the search box suggests; 1 credit),
/hashtag (q = one hashtag: its videos, and on the first page its views and video count; 3 per 20),
/song (id = song id or tiktok.com/music/… link: title, artist, length, videos using it; 1 credit),
/song_videos (id; 2 per 20), /playlist (id = a tiktok.com/@user/playlist/… link; 2 per 20)
and /trending (no input: TikTok's Explore feed; 2 per 20). Lists go up to 300 except followers and following.
instagram
●
●
●
●
●
handle, id (instagram.com/p/… or /reel/… URL), q (one hashtag), limit
facebook
●
●
●
●
—
handle (public page), id (post id or URL), limit. Posts: the newest posts Facebook includes in the page.
# Telegram channel profile
curl "$SW/v1/telegram/profile?handle=durov" -H "Authorization: Bearer $SW_KEY"
# Newest posts in a subreddit
curl "$SW/v1/reddit/posts?handle=r/technology&limit=25" -H "Authorization: Bearer $SW_KEY"
# News search in German
curl "$SW/v1/web/search?q=elektroauto&hl=de-DE&gl=DE" -H "Authorization: Bearer $SW_KEY"
Fetch a profile
GET/v1/{platform}/profileLive
1 creditscope: accounts:read
Look up a channel live: followers, exact video count, total views, join date, country, banner, full bio and external links. A count the platform doesn't show is null, never 0.
Parameter
Type
Default
Description
platformREQUIRED
path
—
youtube
handleREQUIRED
string
—
@handle, channel id (UC…) or channel URL.
hl / gl
string
en / US
Language and region YouTube uses for the response.
One video by id or any YouTube URL (watch, youtu.be, Shorts, live): views, likes, comment count, exact publish time, category, tags, duration and visibility flags.
News search (/v1/web/search) pages through every article Bing News and Google News have for your keywords: hundreds per query, newest first, with total and next_cursor. A search waits a few seconds for article pictures, and the rest keep loading on our side. Send the result links here to get each one's picture and the publisher's own link once ready. Only links from recent searches are looked up.
Parameter
Type
Default
Description
urlsREQUIRED
string[], body
—
Up to 50 result links, as returned by /v1/web/search.
Monitoring: search the posts we collect continuously for your workspace. Email us the brands, keywords and accounts to track, and we keep them up to date. Changes take one email.
Search mentions
GET/v1/mentions/searchLive
5 creditsscope: mentions:read
Search everything your workspace has collected across all platforms. Results are newest first by default.
Parameter
Type
Default
Description
q
string
—
Substring match over post text and author handle.
platform
string, repeatable
all
One of x, youtube, tiktok, facebook, instagram, telegram, reddit, discord, web. Repeat to combine.
Profile statistics built from collected mentions: mention count, total likes and shares, last-seen follower count, first and last seen, and the 3 most recent mentions. This is derived from collected data, not a live profile fetch.
Server-sent events. You get event: connected, then one data: line per new mention, then event: done when the session ends. Use webhooks for 24/7 delivery and the stream for interactive sessions.
import os, requests
with requests.get(
os.environ["SW"] + "/v1/stream",
headers={"Authorization": f"Bearer {os.environ['SW_KEY']}"},
params={"duration": 120},
stream=True,
) as r:
for line in r.iter_lines(decode_unicode=True):
if line.startswith("data:"):
print(line[5:].strip())
Every collected post, comment, video or article is returned as a Mention, whatever platform it came from. Counts a platform doesn't expose are null, never 0.
Field
Type
Description
id
integer
Vanefeed id
platform
string
Source platform
platform_id
string
The platform's own id
author_handle / author_name
string
Who posted it
author_followers
integer | null
Follower count as last seen
author_url
string
Author profile link
text
string
Post text, title + description, or comment body
lang
string | null
Detected language code
url / permalink
string
Link to the original
published_at
ISO-8601 | null
When it was posted. YouTube times are approximate.
fetched_at
epoch seconds
When we collected it
likes, shares, comments_count, views
integer | null
Engagement counts
hashtags
string[]
Hashtags in the text
sentiment_label
positive | neutral | negative
Estimated sentiment
sentiment_score
number, −1 to 1
Strength of the sentiment
sentiment_model
string
Which model scored it
sentiment_why
string
The reason for the score
intent_score / intent_why
0–10, string
Buying-intent estimate and reason
tags, triage_state
string[], string
Your workspace's own labels
Webhook signatures
Deliveries are POST requests with a JSON body and two headers: X-XMonitor-Event (the event name) and X-XMonitor-Signature (sha256= followed by the HMAC-SHA256 of the raw body, keyed with your webhook secret). Verify against the raw bytes, before parsing.
import hashlib, hmac, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["SW_WEBHOOK_SECRET"].encode()
@app.post("/hook")
def hook():
want = "sha256=" + hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(request.headers.get("X-XMonitor-Signature", ""), want):
abort(401)
print(request.headers["X-XMonitor-Event"], request.get_json())
return "", 200
MCP for AI agents Live
The Vanefeed MCP server gives agents in Claude, Cursor and other MCP clients eight tools: get_profile, get_posts, get_post, get_comments, search_platform, search_tracked_mentions, get_usage and get_status. Each result states its credit cost. It's one file with no dependencies and needs Node 18 or newer.
1. Download vanefeed-mcp.mjs. 2. Add it to your client's MCP config with your key:
One-file clients with no dependencies. They handle authentication, paging, retries on rate limits, and expose each call's cost. Download: vanefeed.py (Python 3.9+) · vanefeed.mjs (Node 18+, Deno, Bun, browsers).
from vanefeed import Vanefeed
sw = Vanefeed("sk_live_your_key")
profile = sw.profile("youtube", "@TED")["data"]
print(profile["followers"])
for post in sw.iter_posts("x", "NASA", limit=20, max_pages=3):
print(post["published_at"], post["text"][:80])
print("used", sw.last_cost, "credits; left:", sw.last_remaining)
import { Vanefeed } from "./vanefeed.mjs";
const sw = new Vanefeed("sk_live_your_key");
const { data } = await sw.profile("youtube", "@TED");
console.log(data.followers);
for await (const c of sw.iterComments("youtube", "jNQXAC9IVRw", { limit: 50 }))
console.log(c.author.handle, c.text);
Versioning & deprecation
The version is in the path: /v1. Within a version we only make additive changes: new endpoints, new optional parameters, new fields.
Your code should ignore fields it doesn't know. We never change a field's type or meaning within a version.
Removing or renaming anything in a released version gets at least 90 days' notice by email and in the changelog, with a Deprecation header on affected responses.
Platform changes we can't control (a platform hiding a count) show up as null, never as a broken shape.
Platform status Live
GET /v1/status is public and free, with no key needed. For each platform it reports a state (operational, degraded or outage), the success rate and median and 95th-percentile response time of live calls in the last hour, and the latest background health check. Check it before large batch jobs.