Bowmark AIdocs

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

GlobalWhat it is
bowmarkThe library. Already bound — there is no import step.
log(...args)Records a progress line, returned in logs.
returnWhatever 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, no XMLHttpRequest
  • No filesystem, no process
  • No import, no require

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.

FieldWhat it tells you
incomplete.summaryWhat happened, in one sentence.
incomplete.failuresEach call that threw, and what the site said.
incomplete.degradedEach 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.

  1. needs lists the sites; meta.handoff.url is a single-use link that expires.
  2. Give the user that URL, say which sites it covers, and wait.
  3. 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.