
Company News
Free Business Plan Upgrades for Open Source Maintainers
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.
TypeScript SDK for the Scavio Search API — real-time Google, Amazon, Walmart, YouTube, Reddit, TikTok, TikTok Shop, Instagram, X, and LinkedIn data
TypeScript SDK for the Scavio Search API — real-time Google, Amazon, Walmart, YouTube, Reddit, TikTok, TikTok Shop, Instagram, X, and LinkedIn data.
npm install scavio
import { Scavio } from "scavio";
const client = new Scavio({ apiKey: "sk_..." });
// Google search
const results = await client.search({ query: "web scraping api" });
// Amazon product lookup
const product = await client.amazon.product({ asin: "B09V3KXJPB" });
// Check usage
const usage = await client.getUsage();
const client = new Scavio({
apiKey: "sk_...", // or set SCAVIO_API_KEY env var
baseUrl: "https://api.scavio.dev", // default
timeout: 30_000, // ms, default
maxRequestsPerSecond: 1, // 1-10, default 1
maxRetries: 2, // default 2, set 0 to disable
});
maxRetries is the number of extra attempts after the first request, applied
only to transient failures — HTTP 429, 500, 502, 503, 504 and network or timeout
errors. Backoff is exponential with full jitter, capped at 8s, and a
Retry-After header is honored when the API sends one. Non-transient errors
(400, 401, 402, 404) are never retried.
maxRequestsPerSecond throttles the client so it never sends more than N
requests in any one-second window. Your plan also has a server-side concurrency
limit on simultaneous in-flight requests: 1 on free and pay-as-you-go, 2 on
Project, 3 on Bootstrap, 5 on Startup, 10 on Growth.
Each method hits its own /api/v2/google* endpoint and returns Google's full
response (raw passthrough); each costs 1 credit. Results come back as
organic_results[] with link and snippet. Geo and paging use gl, hl,
start, google_domain and device — the old v1 vocabulary (light_request,
country_code, language, search_type, page) does not exist on v2 and is
dropped server-side.
// SERP search (includes the AI Overview when Google shows one)
await client.google.search({
query: "web scraping",
gl: "us", // optional — geo (country)
hl: "en", // optional — UI language
device: "desktop", // optional
});
// Other Google surfaces
await client.google.aiMode({ query: "how does rag work" });
await client.google.mapsSearch({ query: "coffee near me" });
await client.google.shopping({ query: "laptop" });
await client.google.flights({ departure_id: "JFK", arrival_id: "LAX", outbound_date: "2026-12-15" });
await client.google.hotels({ query: "Bali", check_in_date: "2026-08-01", check_out_date: "2026-08-03" });
await client.google.news({ query: "openai" });
await client.google.trends({ query: "bitcoin" });
Responses are normalized to a stable shape (see the breaking-change note
below); each call costs 1 credit. country is an ISO 3166-1 alpha-2
marketplace code (us, gb, de, ...) and defaults to us.
// Search products
await client.amazon.search({
query: "laptop",
country: "de", // optional, marketplace
page: 2, // optional, 1-based
});
// Get product by ASIN
await client.amazon.product({
asin: "B09V3KXJPB",
country: "us", // optional
});
// Every seller offering that ASIN, with the buy box winner flagged
const res = await client.amazon.offers({ asin: "B09V3KXJPB" });
// res.data -> { asin, title, count, total_offers, has_more_pages, page, offers[] }
// res.data.offers[i] -> { price, currency, condition, seller_name,
// is_buy_box_winner, is_fulfilled_by_amazon,
// shipping_price, list_price, delivery, prime_delivery }
// Supported marketplaces (no API key required)
await client.amazon.options();
There is no sort parameter: the marketplace was verified to ignore every sort value and return the same unordered set, so exposing one would be a filter that silently does nothing.
Amazon moved to a new upstream and the API now returns a normalized shape instead of the previous raw provider payload.
search returns {query, page, total_results, total_results_text, count, products[], filters[], related_searches[]}.
Each product is {asin, title, url, image, price, currency, rating, reviews_count, is_sponsored, position, badge, sales_volume, delivery{is_free, date, fastest_date}}.product returns flat fields: price, list_price, currency, rating, reviews_count, features, images, videos, variants, specifications, best_sellers_rank, shipping, and more. The old buybox[] array no longer exists — use offers for per-seller pricing.offers is new: every seller for one ASIN. 1 credit, page 1 only.country (ISO 3166-1 alpha-2) is the marketplace selector and replaces domain; page replaces start_page. Both old names still work as deprecated aliases.language, currency, device, sort_by, pages, category_id, merchant_id, zip_code, autoselect_variant. Sending one anyway still returns 200, with a top-level warnings array naming what was ignored.options() still returns domains and countries; languages and currencies are now always empty, because neither is a request parameter any more.// Search products
await client.walmart.search({
query: "tv",
min_price: 100, // optional
max_price: 500, // optional
});
// Get product by ID
await client.walmart.product({
product_id: "123456",
});
Credit cost varies by endpoint: transcript costs 8; streams costs 3;
search and shorts cost 2; every other YouTube endpoint costs 1.
// Search videos
await client.youtube.search({
query: "typescript tutorial",
upload_date: "this_week", // optional
sort_by: "relevance", // optional
features: ["hd", "4k"], // optional
cursor: "...", // optional (pagination)
});
// Search Shorts
await client.youtube.shorts({ query: "cooking" });
// Search-as-you-type suggestions
await client.youtube.suggestions({ query: "how to" });
// Get video details (accepts an id or a full watch URL)
await client.youtube.video({ video_id: "dQw4w9WgXcQ" });
// youtube.metadata() is a deprecated alias of youtube.video()
// Video comments and threaded replies
await client.youtube.comments({ video_id: "dQw4w9WgXcQ" });
await client.youtube.commentReplies({
video_id: "dQw4w9WgXcQ",
reply_cursor: "...", // from a comment's reply_cursor
});
// Transcript / subtitles (format: 'text' or 'srt')
await client.youtube.transcript({ video_id: "dQw4w9WgXcQ", format: "srt" });
// Related videos
await client.youtube.related({ video_id: "dQw4w9WgXcQ" });
// Playable / downloadable stream URLs
await client.youtube.streams({ video_id: "dQw4w9WgXcQ" });
// Channels
await client.youtube.channelSearch({ query: "mkbhd" });
await client.youtube.channel({ channel_id: "@mkbhd" }); // id, @handle, or URL
await client.youtube.channelVideos({ channel_id: "UC..." });
await client.youtube.channelShorts({ channel_id: "UC..." });
await client.youtube.channelCommunity({ channel_id: "UC..." });
await client.youtube.channelResolve({ channel: "@mkbhd" }); // handle/URL -> id
Every Reddit endpoint costs 1 credit.
search() takes only query and cursor — there is no result-type or sort
filter upstream, so anything else is dropped server-side. It returns
data.results with next_cursor and has_more. post() returns a flat post
object under data and carries no comments; use postComments() for those.
The subreddit and user feeds return data.posts.
// Search posts
await client.reddit.search({ query: "typescript", cursor: "..." });
// Search-as-you-type suggestions
await client.reddit.searchSuggestions({ query: "python" });
// Post detail (by post_id or url)
await client.reddit.post({ post_id: "t3_1v6ngaf" });
await client.reddit.post({ url: "https://reddit.com/r/typescript/comments/abc123" });
// Post comments and threaded replies
await client.reddit.postComments({ post_id: "t3_1v6ngaf", sort: "TOP" });
await client.reddit.commentReplies({
post_id: "t3_1v6ngaf",
cursor: "...", // reply_cursor from a comment
});
// Subreddit info and feed
await client.reddit.subreddit({ subreddit: "AskReddit" });
await client.reddit.subredditPosts({ subreddit: "AskReddit", sort: "HOT" });
// Redditor profile, posts, and comments
await client.reddit.user({ username: "spez" });
await client.reddit.userPosts({ username: "spez", sort: "NEW" });
await client.reddit.userComments({ username: "spez" });
// Site-wide popular feed and trending searches
await client.reddit.popular();
await client.reddit.trending();
// Search tweets and people
await client.x.search({ search: "artificial intelligence", search_type: "Latest" });
// Tweet detail, comments, and retweeters
await client.x.tweet({ tweet_id: "1808168603721650364" });
await client.x.tweetComments({ tweet_id: "1808168603721650364", rank: "top" });
await client.x.tweetRetweeters({ tweet_id: "1808168603721650364" });
// User profile and feeds
await client.x.user({ screen_name: "elonmusk" });
await client.x.userTweets({ screen_name: "elonmusk" });
await client.x.userReplies({ screen_name: "elonmusk" });
await client.x.userMedia({ screen_name: "elonmusk" });
await client.x.userFollowers({ screen_name: "elonmusk" });
await client.x.userFollowings({ screen_name: "elonmusk" });
// Trending topics
await client.x.trending({ country: "UnitedStates" });
// Person profile, about, and posts. A handle or a full LinkedIn URL works
// anywhere a reference is taken.
await client.linkedin.person({ username: "williamhgates" });
await client.linkedin.personAbout({ url: "https://www.linkedin.com/in/williamhgates/" });
await client.linkedin.personPosts({ username: "williamhgates" });
// Company profile and posts
await client.linkedin.company({ company: "microsoft" });
await client.linkedin.companyPosts({ company: "microsoft" });
// Jobs: search, then pull detail for one listing
await client.linkedin.searchJobs({ search: "software engineer", location: "United States" });
await client.linkedin.job({ job_id: "4415427228" });
// A post and its comments (10 per page)
await client.linkedin.post({ post_id: "7488618410256523265" });
await client.linkedin.postComments({ post_id: "7488618410256523265", page: 1 });
Credit cost varies by endpoint: job costs 30; personPosts, companyPosts,
searchJobs and postComments cost 10 per page; person, personAbout,
company and post cost 1.
Retired endpoints. The upstream provider withdrew the datasets behind
personContact,companyPeople,companyJobs,searchPeopleandsearchPosts. They remain callable but always return HTTP 410 and are never billed.company()returnsfeatured_employees(a small sample of staff), andsearchJobs()with a company name substitutes forcompanyJobs().
personPosts, companyPosts and searchJobs paginate: pass the previous
response's next_cursor as cursor to fetch the next page. personPosts also
takes type ("posts", "comments" or "reactions") to pick the feed.
postComments pages with a 1-based page instead.
// User profile
await client.tiktok.profile({ username: "testuser" });
// User posts
await client.tiktok.userPosts({ sec_user_id: "abc123", count: 30 });
// Video details
await client.tiktok.video({ video_id: "vid123" });
// Video comments
await client.tiktok.videoComments({ video_id: "vid123", count: 20 });
// Comment replies
await client.tiktok.commentReplies({ video_id: "vid123", comment_id: "c456" });
// Search videos (sort_type: '0' = relevance, '1' = most likes)
await client.tiktok.searchVideos({ keyword: "dance", sort_type: "1" });
// Search users
await client.tiktok.searchUsers({ keyword: "cooking" });
// Hashtag info
await client.tiktok.hashtag({ hashtag_name: "fyp" });
// Hashtag videos
await client.tiktok.hashtagVideos({ hashtag_id: "h789", count: 30 });
// User followers
await client.tiktok.userFollowers({ sec_user_id: "abc123" });
// User followings
await client.tiktok.userFollowings({ sec_user_id: "abc123" });
Every TikTok Shop endpoint costs 1 credit. Two limits to design around:
product() resolves only about 44% of the product ids returned by search().
Upstream has no detail data for the rest, so an HTTP 404 is a normal outcome, not an
error — skip the item instead of retrying. Search to product is not a reliable
pipeline. product() throws NotFoundError on that 404 (there is no data
field in the body to test), so a loop over search ids must catch it:
import { NotFoundError } from "scavio";
for (const productId of productIds) {
try {
const detail = await client.tiktokShop.product({ product_id: productId });
} catch (e) {
if (e instanceof NotFoundError) continue; // no detail upstream; skip
throw e;
}
}
productReviews() often works for ids product() cannot resolve: of 8 such ids
tested, 8 returned HTTP 200 and 7 carried at least one review, so it is a useful
fallback source of product detail.
product() does not return a price; upstream masks it on the product page. Exact
prices come from search(), shopProducts(), and categoryProducts().
// Search products (US catalog, exact prices, cursor pagination)
await client.tiktokShop.search({ search: "phone case" });
// Keyword suggestions (8 regions)
await client.tiktokShop.searchSuggestions({ search: "wireless", region: "US" });
// Product detail (no price; a 404 is normal, see above)
await client.tiktokShop.product({ product_id: "1732293553906094315" });
// Product reviews (up to 200 per call)
await client.tiktokShop.productReviews({
product_id: "1732293553906094315",
page_size: 200,
sort: "relevant",
});
// Category tree (28 top-level, 240 nodes)
await client.tiktokShop.categories();
// Products in a category (US and GB only)
await client.tiktokShop.categoryProducts({ category_id: "601450" });
// A shop's catalog, 30 per page
await client.tiktokShop.shopProducts({ shop_id: "7495514739648989419" });
// Resolve any TikTok Shop URL or share link to a product_id / shop_id
await client.tiktokShop.resolve({ url: "https://vt.tiktok.com/ZT2AHoGsE/" });
Credit cost varies by endpoint, in three tiers:
| Credits | Methods |
|---|---|
| 2 | userPosts |
| 8 | post, commentReplies |
| 10 | profile, userReels, userTagged, userStories, postComments, searchUsers, searchHashtags, userFollowers, userFollowings |
The 10-credit endpoints run two upstream providers in parallel and bill both legs; the 8-credit ones have no fallback leg to hedge against.
// User profile
await client.instagram.profile({ username: "instagram" });
// User posts / reels / tagged
await client.instagram.userPosts({ username: "instagram", count: 12 });
await client.instagram.userReels({ username: "instagram" });
await client.instagram.userTagged({ username: "instagram" });
// User stories
await client.instagram.userStories({ username: "instagram" });
// Post detail (by url, media_id, or shortcode)
await client.instagram.post({ shortcode: "DUajw4YkorV" });
// Post comments and replies
await client.instagram.postComments({ shortcode: "DUajw4YkorV", sort_order: "newest" });
await client.instagram.commentReplies({ media_id: "123", comment_id: "456" });
// Search
await client.instagram.searchUsers({ keyword: "justin" });
await client.instagram.searchHashtags({ keyword: "fashion" });
// Followers / followings
await client.instagram.userFollowers({ username: "instagram", count: 50 });
await client.instagram.userFollowings({ username: "instagram" });
const usage = await client.getUsage();
import { Scavio, InvalidAPIKeyError, RateLimitError } from "scavio";
try {
const results = await client.search({ query: "test" });
} catch (error) {
if (error instanceof InvalidAPIKeyError) {
// 401 — bad API key
} else if (error instanceof RateLimitError) {
// 429 — too many requests
}
}
All error classes:
| Class | HTTP Status | Description |
|---|---|---|
MissingAPIKeyError | — | No API key provided |
ScavioConnectionError | — | Request never reached the API (DNS, reset, TLS) |
ScavioTimeoutError | — | Request exceeded the configured timeout |
BadRequestError | 400 | Invalid request parameters |
InvalidAPIKeyError | 401 | Invalid API key |
InsufficientCreditsError | 402 | No credits remaining |
NotFoundError | 404 | No data upstream for that id (see TikTok Shop above) |
RateLimitError | 429 | Rate limit exceeded |
ScavioAPIError | other | Catch-all (has .statusCode) |
Every class extends ScavioError, so catch (e) { if (e instanceof ScavioError) }
matches all of them. All except MissingAPIKeyError, ScavioConnectionError and
ScavioTimeoutError carry .statusCode and .responseBody.
Zero dependencies — uses native fetch.
MIT
Scavio is a unified search API for AI agents — one API key, structured JSON, no scraping or proxies. A real-time Tavily alternative and SerpAPI alternative with data from:
Teams choosing between providers can compare Scavio vs alternatives side by side.
Get a free API key and explore the documentation.
FAQs
TypeScript SDK for the Scavio Search API — real-time Google, Amazon, Walmart, YouTube, Reddit, TikTok, TikTok Shop, Instagram, X, and LinkedIn data
We found that scavio demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Company News
Open source maintainers are under more pressure than ever. We're raising our open source program from the Team plan to the Business plan, free.

Security News
The supply chain control that delays freshly published gems now covers lockfile generation and gem vendoring in Ruby projects.

Security News
During a UK cyber test, a Mythos 5 agent used sockpuppets, social engineering, and prompt injection to try to get a maintainer to merge malware.