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.
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.