How it works
Two calls. Read the library, then run a script against it on the live sites.
Every Bowmark task is the same two calls, in this order.
1 — Read the library
get_library({ query }) returns the callable functions that match what you want to
do, with their argument shapes and return types.
It touches no website. It is one read-only call, and an unrecognized query returns a one-line index of the whole library rather than an error — so the check never dead-ends and never costs you an attempt.
const library = await get_library({ query: "flights" });Pass what you want to do (flights, price a GPU) or a company if one was named
(Kayak). You get what you asked about and nothing else.
Every response is bounded and says so when it is a slice. When it does, absence from the list proves nothing — the fix is a narrower query, not a conclusion.
2 — Run a script
You write plain async JavaScript against what came back, and run executes it in a
sandbox. The sandbox reaches the live sites; your script cannot.
const { flights, warnings } = await bowmark.flights.search({
from: "SFO",
to: "JFK",
depart: "2026-09-01",
});
return { flights: flights.slice(0, 3), warnings };Bowmark picks the cheapest way to reach each site — a plain HTTP request where that works, a real browser only where the site forces one — and fans a capability out across every site that can serve it.
There is no third call. What comes back is { ok, status, result, logs, error, ms }.
The same two calls over plain HTTP
Nothing above needs an MCP client. The two calls are two HTTP endpoints, so an agent with only a shell or a fetch tool can use Bowmark with nothing installed.
curl -s "https://api.bowmark.ai/v1/library?query=flights"curl -s -X POST https://api.bowmark.ai/v1/run \
-H 'Content-Type: application/json' \
-d '{"script":"const { flights } = await bowmark.flights.search({from:\"SFO\",to:\"JFK\",depart:\"2026-09-01\"}); return flights.slice(0,3);"}'Add -H "Authorization: Bearer $BOWMARK_API_KEY" to raise the per-IP cap and unlock
sites that need the user signed in. See API keys.
Why a script instead of a tool call
One script, several calls, combined however the task needs. That is the thing you cannot do by driving a browser step by step, and it is the reason the surface is a language rather than a menu of tools.
const dates = ["2026-09-01", "2026-09-02", "2026-09-03"];
const runs = await Promise.all(
dates.map((depart) => bowmark.flights.search({ from: "SFO", to: "JFK", depart })),
);
return runs.flatMap((r) => r.flights).sort((a, b) => a.price - b.price)[0];Three days of fares, ranked, in one round trip.
The language itself is a separate topic — the globals you get, what is forbidden inside the sandbox, and how to read the response envelope are all in Scripting.
And you do not have to be an agent to do any of this. npm i @bowmark/web or
pip install bowmark-web gives you the same library as typed functions in your own
process, with autocomplete and no script string — see
Scripting.
Two tiers in the library
Capabilities — bowmark.flights.search(...). The default. One call fans out
across every site that can serve the task and routes around the ones that fail.
Providers — bowmark.providers.kayak.search(...). One specific site. Reach for
one when you want that site's answer rather than the best available.
Prefer the capability unless you have a reason. It is the one that survives a site going down.