Hermes Agent can drive a headless browser and it can call HTTP tools. The two are not interchangeable, and choosing wrong is the most common reason an agent that worked last week is failing today. Use browser automation for pages behind a login, form submissions and interactive flows. Use a structured API for everything else. Default to the API and add the browser only where the target genuinely requires interaction.
This holds across releases. Hermes ships every few days and the browser integration has been reworked more than once, but the tradeoff below is about where the work happens -- your machine or someone else's -- not about which version you are on.
When browser automation is the right call
- The target is behind authentication and you hold the session
- You need to fill forms, click through a multi-step flow, or trigger something
- The data lives in a JavaScript-rendered app with no structured source behind it
- You need a screenshot for visual verification
- It is your own application and you are testing it end to end
When a search API is the right call
- Finding information across search engines, video, or discussion sites
- Product research across retail platforms
- Anything where you want JSON, not HTML you then have to parse
- Anything running unattended, where a silent failure costs you a whole run
The distinction is not "hard vs easy". It is who absorbs the maintenance. A browser tool makes your agent responsible for the target's markup, its bot detection, and its next redesign. An API tool moves that to a provider whose job it is.
Wiring the API tool into Hermes
# tools.py -- Scavio as a Hermes tool
import os
import requests
API = "https://api.scavio.dev"
H = {
"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}",
"Content-Type": "application/json",
}
def web_search(query: str) -> list:
"""Search Google and return organic results as structured JSON."""
r = requests.post(
f"{API}/api/v2/google", headers=H, json={"query": query}, timeout=30
).json()
return [
{
"title": item["title"],
"url": item["link"],
"snippet": item.get("snippet", ""),
}
for item in r.get("organic_results", [])[:5]
]
def youtube_search(query: str) -> list:
"""Search YouTube. v1 endpoints nest their payload under `data`."""
r = requests.post(
f"{API}/api/v1/youtube/search", headers=H, json={"search": query}, timeout=30
).json()
return r.get("data", {}).get("results", [])
def youtube_transcript(video_id: str) -> str:
"""Full transcript as plain text -- one synchronous call, no yt-dlp."""
r = requests.post(
f"{API}/api/v1/youtube/transcript",
headers=H,
json={"video_id": video_id, "format": "text"},
timeout=60,
).json()
return r.get("data", {}).get("content", "")
TOOLS = [web_search, youtube_search, youtube_transcript]Three things to carry over into your own code. Every endpoint is a POST with a JSON body. Auth on the REST API is Authorization: Bearer -- the x-api-key header belongs to the MCP server, not to this. And the envelope differs by endpoint family: the Google endpoint returns its payload at the top level, while /api/v1/* platform endpoints nest theirs under data, alongside response_time, credits_used and credits_remaining.
The third option: bundled scraper skills
There is a middle path people reach for before either of the above, and it deserves naming because it is the one that fails quietly. Hermes ships a youtube-content skill built on youtube-transcript-api and yt-dlp. It is free and it is right there, and it breaks on a schedule: undeclared dependencies that blow up on a clean install, transcript fetches that fail behind a proxy, a Shorts filter that stopped matching after a layout change.
This is not a knock on the skill. It is the structural cost of sitting directly on an unofficial surface with nobody contracted to keep up with it. For a laptop experiment that tradeoff is fine. For an agent running unattended it is a dependency that will fail at some point on a timetable you do not control -- and, worse, will often fail by returning nothing rather than raising.
If YouTube data is load-bearing, the endpoints above cover the same ground with an owner on the other end.
How the three actually compare
No table of invented benchmark numbers here, because the numbers that matter are yours. What is worth comparing is the shape of each option:
| Headless browser | Structured API | Bundled scraper skill | |
|---|---|---|---|
| Latency | Full page load plus JS execution, per target | One request, parsed JSON back | One request, but often several under the hood |
| Cost model | Your CPU, RAM and proxy bandwidth, plus retries | Credits per call, known in advance | Free until it costs you a failed run |
| Fails by | Timeout, selector miss, bot challenge | HTTP status you can branch on | Returning empty and looking successful |
| Maintenance owner | You, on the target's redesign schedule | The provider | Whoever last touched the upstream library |
| Right for | Auth-gated, interactive, your own app | Public structured data at volume | Prototypes and one-off scripts |
For a concrete cost anchor on the middle column: at list pricing of $30 per month for 7,000 credits, a 1-credit call works out to roughly $0.004. Credit cost varies by platform rather than being flat -- Google, Amazon, Walmart, eBay, Target, Airbnb, Zillow, Redfin, SEC EDGAR, Companies House and Meta Ads are 1 credit; Threads, Yelp, Tripadvisor, Indeed, Capterra, Google Play and Home Depot are 2; G2 is 5; Kuaishou runs from 1 to 40 by endpoint. Read credits_used on the response if you want the figure from the source rather than from a table.
The browser column has no honest single number. It depends on your proxy provider, how many retries a blocked page costs you, and whether you are paying for the machine that runs the browser. Measure it on your own targets before assuming it is cheaper.
Hybrid pattern
Try the API, fall through to the browser only when the target genuinely needs it:
const API = "https://api.scavio.dev";
const H = {
Authorization: `Bearer ${process.env.SCAVIO_API_KEY}`,
"Content-Type": "application/json",
};
async function smartSearch(query, { needsBrowser = false } = {}) {
if (!needsBrowser) {
const resp = await fetch(`${API}/api/v2/google`, {
method: "POST",
headers: H,
body: JSON.stringify({ query }),
});
if (resp.ok) return (await resp.json()).organic_results ?? [];
// Branch on the status rather than swallowing it: 401 is a bad key,
// 402 is out of credits, 429 is rate limiting. None are browser problems.
if (resp.status !== 404) throw new Error(`scavio ${resp.status}`);
}
// Auth-gated or interactive: this is where the Hermes browser tool earns its place
return browserFallback(query);
}The fallback should be reached deliberately, not automatically. An agent that silently switches to a browser on every API hiccup will mask a bad key or an empty credit balance as slowness for days.
Installing it as a skill instead
If you would rather not hand-write the tool functions, Scavio publishes 50 skills on ClawHub covering all 50 platforms, installable directly inside Hermes:
hermes skills install @scavio-ai/scavio-amazon
hermes skills install @scavio-ai/scavio-youtube
export SCAVIO_API_KEY=sk_live_your_keyInstall one platform at a time. Every skill you add is context the model reads on every turn, and a short tool list beats a comprehensive one on local backends.
Decision framework
- Can a search query get you the data? Use the API.
- Is the target public with structured content behind it? Use the API.
- Does it require login, a form fill, or real interaction? Use the browser.
- Is it your own app, and are you verifying behaviour? Use the browser.
- Is it running unattended? Use the API -- the failure mode is a status code you can act on, not an empty result that looks like an answer.
- Is it a throwaway script you will run once? Use whatever is already installed, and do not build on it.
New accounts get 50 free credits on signup, one time, no card required -- enough to run both paths against your own targets and settle the question with your numbers instead of anyone else's. Get a key, or read the endpoint docs first.