
Security News
GitHub Actions Adds cache-mode to Limit Cache Poisoning Risk
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.
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 API — real-time web scraping and data extraction across 31 platforms on one API key: Google SERP, Amazon, Walmart, eBay, Zillow, Redfin, Booking, Airbnb, Yelp, Indeed, Glassdoor, G2, App Store, Google Play, SEC EDGAR, Google
The npm package scavio receives a total of 71 weekly downloads. As such, scavio popularity was classified as not popular.
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.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.