Scripting
The language you write against the library — globals, sandbox rules, composition, and how to read the response.
Bowmark's surface is a language, not a menu of tools. You send plain async JavaScript; it runs in a sandbox with the library bound to a global.
This page is the language itself. For where it sits in the flow, see How it works.
The shape of a script
Write a plain async body, not a wrapping function.
const { flights, warnings } = await bowmark.flights.search({
from: "SFO",
to: "JFK",
depart: "2026-09-01",
});
return { flights: flights.slice(0, 3), warnings };What you get
| Global | What it is |
|---|---|
bowmark | The library. Already bound — there is no import step. |
log(...args) | Records a progress line, returned in logs. |
return | Whatever you return comes back as result, JSON-serialized. |
Real control flow is available: if, loops, map / filter / sort / slice, and
Promise.all for fan-out.
Every library function is async. Always await. A forgotten await returns a
pending promise, which serializes to {} and looks like an empty result.
What is forbidden
bowmark is the only I/O.
- No
fetch, noXMLHttpRequest - No filesystem, no
process - No
import, norequire
The sandbox has hard CPU, memory and wall-clock limits, so keep scripts small and deterministic. No infinite loops.
This is a real boundary, not a lint rule
Scripts run off-process in a V8 isolate. A script that reaches for fetch does not
get a warning — the identifier is not there.
Composition is the point
One script, several calls, combined however the task needs. This is what you cannot do by driving a browser step by step.
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 {
cheapest: runs
.flatMap((r) => r.flights)
.sort((a, b) => (a.price ?? 1e9) - (b.price ?? 1e9))
.slice(0, 5),
warnings: runs.flatMap((r) => r.warnings),
};Fan out with Promise.all, then reduce in the same script. One round trip.
Reading the response
You get { ok, status, result, logs, error, ms, trace }.
Check status before ok. It is ok | error | partial | needs_user.
partial — it ran, the answer is narrower than you asked for
The script ran and result is real, but some of what it called never answered.
ok is still true. This is not a failure.
| Field | What it tells you |
|---|---|
incomplete.summary | What happened, in one sentence. |
incomplete.failures | Each call that threw, and what the site said. |
incomplete.degraded | Each call that answered while reporting its own results thin. |
Say so when you present the result. Name what was missed, and never call it complete, exhaustive, or "all" of anything.
Check incomplete.failures[].fixable before treating it as final. fixable: true
means your argument was rejected, not the site — the error names what that function
really takes, so fix it and run again. For anything else, re-running rarely helps.
needs_user — a site needs the user signed in
A pause, not a failure, and not something a script edit can fix.
needslists the sites;meta.handoff.urlis a single-use link that expires.- Give the user that URL, say which sites it covers, and wait.
- When they say they are done, send the same script again, unchanged.
Do not retry before then — it will stop in the same place and cost another run. Do not try to sign in yourself, and do not ask the user for a password.
Signed-in runs need a key
Without an API key on the request you are told to add one rather than handed a handoff link. See API keys.
warnings is the only thinness signal
Some capabilities return { rows, warnings } (flights, hotels, cars); others return a
bare array — check the signature.
Where there is one, warnings names any site dropped from that search, and it is the
only signal that an answer is thin rather than complete. A result with warnings you
did not read is a result you cannot describe honestly.
Providers vs capabilities
// Capability — fans out across every site that can serve the task,
// routes around the ones that fail. The default.
const { flights } = await bowmark.flights.search({ from: "SFO", to: "JFK", depart });
// Provider — one specific site, when you want THAT site's answer.
const rows = await bowmark.providers.kayak.search({ from: "SFO", to: "JFK", depart });Prefer the capability unless you have a reason to pin a site.