Every change Wellknown observed on this MCP server, newest first, with what it was before and what it became. Tool-surface changes carry the definition diff. Nothing here is edited after the fact.
Added "session_call", "session_close" and "support"; changed the definition of "report" (13 tools before, 16 now)
⟨145 unchanged words⟩ run. For an account holder the result mayalso carry `supportRoom`,offer a live chat with Bowmark support:aboutaskthisyourreport:user,followandtheifinstructionstheythatsaycomeyes, use `support` withit.the `reportId` it returns. When the same task keeps failing (it ⟨36 unchanged words⟩
⟨3 unchanged words⟩ {"ok":{"type":"boolean"},"supportRoomreportId":{"additionalProperties":false,"description":"A live Bowmark supportPasschatthisabouttothis`support`report,tofortalkaccountaboutholders.it.","properties":{"joinPrompt":{"type":"string"},"urlsupportOffered":{"typedescription":"string"}},"required"True when live support is available:["url","joinPrompt
Call one Bowmark function in a session that keeps its browser — Runs ONE library function in a Bowmark session, so a flow that needs several calls in the SAME browser can finish: a booking that texts the user a code, a cart filled over several steps, a form that waits on an answer from the user. `run` cannot do these, because every run starts a fresh browser and the held page dies with it. `path` is the function as a script writes it after `bowmark.`, split on the dots: `["providers","opentable","startBooking"]`. `args` are its arguments, in order. Look both up in `get_library` first. Omit `sessionId` on the first call: a session opens and its `sessionId…
End a Bowmark session — Ends a session `session_call` opened and frees its browser. Pass the `sessionId`. Closing one that is already closed is harmless.
Talk with Bowmark support about a report — Chat live with a Bowmark support agent about a problem you reported. Use it only after your user said yes to talking to support. Pass the `reportId` that `report` returned. Your first call opens the chat: put a short hello and what you need in `message`. Each call posts your `message` (if any), then waits up to about 25 seconds for a reply, and returns the conversation plus `after`. Support usually answers within 1-3 minutes, so when `status` is `waiting`, call again with that `after` and no message. When `status` is `answered`, tell your user what support said, and reply with `message` to ke…
Changed the definition of "get_library"
**Use this whenever a task touches a live website.** It answers,definitively and cheaply,whether Bowmark can already DO the thing: current ⟨95 unchanged words⟩ touched, and a query that matches nothingreturns a one-line indexsaysinsteadsoofratheranthanerror.erroring. If nothing fits, you have lost one ⟨28 unchanged words⟩ (`"Kayak"`, `"newegg.com"`).IfOnlythea
Added "wait_for_sign_in"; changed the definition of "add_connection" and "run" (12 tools before, 13 now)
⟨115 unchanged words⟩ to send the login that Chrome already holds.WhenThentheycallsay`wait_for_sign_in`theywitharethedone,returned`list_connections``ref`:showsit answers thenew
Added "lease_ip" (11 tools before, 12 now)
Get the user a dedicated IP for sites that need one — Some sites (LinkedIn, marked 📍 in `get_library`) only accept a login from the address it signed in from. A dedicated IP is one address that only this account uses, and once the account holds one, every sign-in link and every later call on that login uses it automatically. Call with no arguments first. If the account already holds one, it says so and nothing is bought. Otherwise it returns `priceCents` and a `message` with the price for `termDays` days in `country` (default US). The price is charged up front and is not refundable. If something must happen first (a card on file, a one-time id…
Added "add_connection"; changed the definition of "list_connections" and "report" (10 tools before, 11 now)
⟨148 unchanged words⟩ id, so you can keep using it.AddingTo add a login for a site with none,iscallthe`add_connection`user's—toitdo,handsatyouhttps://bowmark.ai/dashboard/connectionsa
Changed the definition of "run"
⟨20 unchanged words⟩ items":{},"type":"array"},"notes":{},"notice":{},"ok":{"type":" ⟨104 unchanged words⟩
Changed the definition of "run"
⟨24 unchanged words⟩ ,"ok":{"type":"boolean"},"queuedMs":{"type":"number"},"result":{},"runId":{"format":" ⟨97 unchanged words⟩
Changed the definition of "report"
⟨138 unchanged words⟩ feedback only and does not retry a run. For an account holder the result may also carry `supportRoom`, a live chat with Bowmark support about this report: follow the instructions that come with it.
⟨3 unchanged words⟩ ":{"ok":{"type":"boolean"},"supportRoom":{"additionalProperties":false,"description":"A live Bowmark support chat about this report, for account holders.","properties":{"joinPrompt":{"type":"string"},"url":{"type":"string"}},"required":["url","joinPrompt"],"type":"object"}},"required":["ok"],"type":"object"}
Changed the definition of "get_library"
⟨223 unchanged words⟩ an entry's name for its types and examples. Several parts? Pass `queries: ["amazon", "youtube"]`, one per part, in ONE call. **Read the first lines of the answer.** A ⟨94 unchanged words⟩
{"additionalProperties":false,"properties":{"queries":{"description":"Optional. Several lookups in ONE call, one per part of the task: `[\"amazon\", \"youtube\"]`, `[\"flights\", \"hotels in Lisbon\"]`. Each entry is written exactly like `query` and answered exactly as `query` alone would be, in its own section. Use this instead of calling get_library once per part. At most 6; a `query` passed alongside is folded in.","items":{"type":"string"},"type":"array"},"query":{"description":"Optional, but pass ⟨118 unchanged words⟩
Changed the definition of "get_library", "report" and "run"
⟨14 unchanged words⟩ whether Bowmark can already DO the thing:look upcurrent prices,checkreal availability or stock,searcha site,getsearch, a quote or a fare,drivea configurator,starta booking,or pullanythingthat only existsbehind a form or a filter. **Behind a LOGIN is narrower**: it worksoncewhere a purpose-built provider exists for that site,or the caller has a saved connection there— a generic caller with neither gets only a form-field reader
Changed the definition of "list_connections" and "run"
List the saved logins this account already holds,—onetheentry`id`pertologin: its `id` (name it in alatersigned-in call as `{ connection: id }`),whichthe site, which account on that site
Changed the definition of "delete_connection", "list_connections" and "logout_connection"
ForgetAskonetheofuserthetouser'sconfirm deleting a savedloginslogin
ForgetStart
Added "run_view" (9 tools before, 10 now)
Run view — Used by the inline run view to draw a run's progress. You do not need to call this — `run` returns the result.
Changed the definition of "run"
⟨301 unchanged words⟩ `. Return the link rather than inlining thebytes.bytes — UNLESS you need to read the text yourself (a transcript you will summarize): then return the part you need, trimmed (e.g. `text.slice(0, 4000)` per item), beside the link in the SAME run. Fetching each saved link back afterwards costs one tool call per file (measured 2026-10-02: 15 transcripts, ~100s). Keep the whole result under ~25,000 characters: clients reject or spill a larger tool result (Claude Code moved a 50 KB one to a file the model could not open), and the answer is lost. • Keep scripts small and deterministic — no ⟨80 unchanged words⟩ text as `run({ script })` —`script` is the only argument (there is no `site` argument; the library exposes every capability under`bowmark``bowmark`. A run still going after ~40 seconds answers `status: "running"` with its `runId` instead of letting your tool call time out: it keeps executing on Bowmark's side, so call `run({ runId }).` to wait for that SAME run — never send the script again, which would do the work twice. `result` is whatever you returned; `logs` are your
Changed the definition of "run"
⟨619 unchanged words⟩ failure re-running usually returns the same thing. • When a fan-out capability (shipping, flights, hotels, cars, etc.) fails on EVERY provider, `incomplete.failures[].dropped` carries the per-provider breakdown as an array of `{ provider, fn, reason, detail }` — branch on that instead of parsing the prose in `.error`. • `needs_user` means a site needs the USER signed ⟨696 unchanged words⟩ TOGETHER inside `Promise.all` — in parallel they costabout what one call costs, not the sum of them — and never call them one after another. To sweep a date range, call the search per date inside `Promise.all` and sort/filter the merged array (each flight result carries its `date`, so you can tell the runs apart). See th
Added "delete_connection" and "logout_connection"; changed the definition of "list_connections" and "run" (7 tools before, 9 now)
List the saved logins this account holds — the `id` to name in a later signed-in call as `{ connection }`, which site, which account, whether it is ⟨48 unchanged words⟩ and ask the user to sign in again.Revoking one is deliberately notAdding atool —NEWitlogin is still the user's to do, at https://bowmark.ai/dashboard/connections.//bowmark.ai/dashboard/connections — `delete_connection` only forgets one this account already holds.
Changed the definition of "get_library"
⟨46 unchanged words⟩ pull anything that only exists behind a form or a filter. **Behind a LOGIN is narrower**: it works once a purpose-built provider exists for that site, or the caller has afiltersaved connection there — a generic caller with neither gets only a form-field reader, which cannot pass an identity check or obtain alogin.vendor API key on its own. **Use this for Bowmark questions too.** Before answering ⟨569 unchanged words⟩
Changed the definition of "run"
⟨230 unchanged words⟩ async body, not a wrapping function. • `bowmark.files` keeps a file the run produces (a CSV, an image, a transcript) in the caller's Bowmark account and gives you a link to it: `save({ name, text | base64, contentType? })` → `{ id, url, expiresAt }` (25 MB from a script), `list()`, `get(id)`, `url(id, { expiresIn })`, `makePublic(id)` → `{ publicUrl }`, `makePrivate(id)`, `delete(id)`. Return the link rather than inlining the bytes. • Keep scripts small and deterministic — no infinite ⟨1059 unchanged words⟩ warnings }`. Others return a bare array.The signature in `get_library` tells you which; go by it rather than assuming. Where there IS a `warnings` array it names any site dropped from the fan-out, and the rows themselves look identical with or without it. Read it, and pass on anything it says rather than quoting a 'cheapest' that only ranks the sites that happened to answer. Dropping `warnings` from what you return does not hide it — theTh
Changed the definition of "get_secret_link", "list_secrets", "request_secret" and 1 more
Return the dashboard link for one storedcredentialsecret this account holds, so you can answer ⟨42 unchanged words⟩
List the storedcredentialssecrets this account holds, by NAME. Returns name, ⟨64 unchanged words⟩
Changed the definition of "run"
⟨670 unchanged words⟩ the user to add one rather than retrying. ONE EXIT IP FOR THE WHOLE SCRIPT: `await bowmark.egress.pin({ ttlMinutes: 120 })` before your first call, and every call the script makes afterwards leaves from ONE address. Reach for it when a site binds a cart, a token or a sign-in to the address that made the request, and the symptom is being logged out or asked to start over between steps. It is not a dedicated IP: the exit is a shared residential peer, `shared: true` says so, and the vendor can still replace it without telling us. **Pin FIRST** — a site you already called keeps the address it used, and the result says `pinnedAfterCalls: true` when you left it too late. Up to 7 days; anything longer comes back clamped, and the `ttlMinutes` you get back is the one that was granted. SAVED CREDENTIALS: `savedSecrets` lists every credential the run ⟨550 unchanged words⟩ `warnings` from what you return does not hideit — the run comes back `status: 'partial'` regardless, because the runtime counts what your script CALLED, not what it chose to report. WHEN NO FUNCTION FITS, OR ONE FAILED ON THE SITE ITSELF: `bowmark.browser_agent` hands the task, in plain language, to a hosted AI browser agent. It costs real money per turn, charged to the account, so use it only after `get_library` had nothing for the site or a function's failure was not `fixable`. `start({ task })` returns at once with an `id` and a `watchUrl`. Give the user that link: they can watch the agent and take over the browser to sign in or clear a check. The agent keeps working between your runs, so in LATER runs call `status(id, { waitMs: 60000 })`; when it is `needs_input`, relay `quest
Changed the definition of "run"
⟨804 unchanged words⟩ is the receipt. COMPOSITION MEANS PARALLEL, NOTSEQUENTIAL.SEQUENTIAL, AND THAT GOES FOR PROVIDER CALLS TOO — an `await` inside a `for` loop is the single slowest thing you can write here. Measured 2026-09-18 over 147 real runs: a script with one awaits a for-loop body ran a median of 49 seconds against 19 for the rest, and on YouTube transcripts specifically, three fetched one after another took 27.3s where six fetched together took 9.8s. Collect the ids first, then `Promise.all` over them; never walk a list awaiting each item. Default to ONE capability call per script — ⟨145 unchanged words⟩ more into one script to make it fit. A FAN-OUT OF FIVE THAT LOSES ONE SHOULD STILL HAND BACK FOUR: `Promise.all` rejects the whole thing the moment any leg does, so one refused request turns a finished answer into nothing. Use `Promise.allSettled` and keep the fulfilled legs. That is safe HERE and nowhere else, because Bowmark reports the dropped leg for you — the runtime counts what your script CALLED at the dispatch point, not what it chose to report, so a swallowed failure still comes back as `status: 'partial'` with the call named in `incomplete`. Say so when you present the result. SOME capabilities return their rows alongside a `warnings` ⟨212 unchanged words⟩ })`; when it is `needs_input`, relay`question` to the user and answer with `send(id, message)`; when it is `idle`, read `result` and call `stop(id)`. Always `stop` a session you are finished with. `get_library({ query: "browser agent" })` has the types.
Changed the definition of "get_library"
⟨417 unchanged words⟩ supports the conclusion that a task is uncovered.**Two**THREE tiers comeback.**back, and the third is not written by Bowmark at all.** SITE TOOLS (`bowmark.sites["skims.com"].search_catalog(...)`) are the tools a SITE publishes about ITSELF — its own MCP server, or the tools its pages register — read live at the moment you name the host and callable like anything else. Nothing has to have been built for that site in advance, so **naming a site is worth doing even when you expect nothing to exist for it.** The descriptions are the site's own words, every tool it exposes is callable (including ones that change something there), and site tools are free. **The other two tiers come back too.** CAPABILITIES (`bowmark.flights.search(...)`) are the default ⟨91 unchanged words⟩
⟨60 unchanged words⟩ rather than the full URL or its queryparameters.
Changed the definition of "run"
⟨1083 unchanged words⟩ script CALLED, not what it chose to report. WHEN NO FUNCTION FITS, OR ONE FAILED ON THE SITE ITSELF: `bowmark.browser_agent` hands the task, in plain language, to a hosted AI browser agent. It costs real money per turn, charged to the account, so use it only after `get_library` had nothing for the site or a function's failure was not `fixable`. `start({ task })` returns at once with an `id` and a `watchUrl`. Give the user that link: they can watch the agent and take over the browser to sign in or clear a check. The agent keeps working between your runs, so in LATER runs call `status(id, { waitMs: 60000 })`; when it is `needs_input`, relay `question` to the user and answer with `send(id, message)`; when it is `idle`, read `result` and call `stop(id)`. Always `stop` a session you are finished with. `get_library({ query: "browser agent" })` has the types.
{"additionalProperties":false,"properties":{"costemptyHanded":{"additionalProperties":false}
Changed the definition of "run"
⟨670 unchanged words⟩ the user to add one rather than retrying. SAVED CREDENTIALS: `savedSecrets` lists every credential the run stored in the user's Bowmark account, each as `{ name, type, viewUrl }`. An API key a key-making function returns is saved there automatically as `<vendor>_api_key`, and later runs that call that vendor use it with nothing to configure. When `savedSecrets` is present, tell the user what was saved and give them each `viewUrl`, where they can view or manage it. `trace` is the execution trace — every capability ⟨336 unchanged words⟩
Certificate changed, valid to 2026-12-15
Added "get_secret_link", "list_connections", "list_secrets" and 1 more; changed the definition of "run" (3 tools before, 7 now)
⟨72 unchanged words⟩ ":"uuid","type":"string"},"savedSecrets":{"items":{"additionalProperties":false,"properties":{"name":{"type":"string"},"type":{"type":"string"},"viewUrl":{"type":"string"}},"required":["name","type","viewUrl"],"type":"object"},"type":"array"},"secretNeeds":{"items":{"additionalProperties":false,"properties":{"expiredAt":{"type":"string"},"reason":{"enum":["expired","pending","unavailable"],"type":"string"},"secret":{"type":"string"}},"required":["secret","reason"],"type":"object"},"type":"array"},"status":{"enum":["ok","error" ⟨8 unchanged words⟩
Get the user a link to one of their own stored logins — Return the dashboard link for one stored credential this account holds, so you can answer "where is my Acme password?" with somewhere to go. **It returns a URL, never a value** — opening it requires the user to be signed in and to confirm who they are, and only their own browser can read what comes back.
Removed "register"; changed the definition of "run" (4 tools before, 3 now)
{"additionalProperties":false,"properties":{"cost":{"additionalProperties":false,"properties":{"lines":{"items":{"additionalProperties":false,"properties":{"quantity":{"type":"number"},"sku":{"type":"string"},"unit":{"enum":["bytes","ms","count"],"type":"string"},"usd":{"type":"number"}},"required":["sku","quantity","unit","usd"],"type":"object"},"type":"array"},"usd":{"type":"number"}},"required":["usd","lines"],"type":"object"},"emptyHanded":{},"error":{"type":[ ⟨14 unchanged words⟩ :{"items":{},"type":"array"},"notice":{},"ok":{"type":"boolean"}, ⟨18 unchanged words⟩
Create a free Bowmark account for more calls — **Creates a free Bowmark account and returns an API key.** Call it when a `run` is refused for hitting the anonymous limit, when you expect to make more than a handful of calls, or any time the user says they want an account. **Every argument is optional. `register({})` is a complete, valid call** and returns a working key. Do NOT stop to ask the user for anything before calling this — there is nothing required to ask for. WHAT IT BUYS: anonymous callers share one small daily allowance per IP address with everything else behind it. A registered account gets its own monthly allowance, an ord…
Certificate recorded, valid to 2026-10-17
Authorization not required
Changed the definition of "get_library"
Check whatcan be done on aBowmarklivecanwebsitedo for this task
⟨52 unchanged words⟩ a form, a filter, or a login. **Use this for Bowmark questions too.** Before answering how to write or run a Bowmark script, what the sandbox supports, or how to start when no site or task is named, call it with the caller's words. The matching entry gives the platform guidance; guessing from general programming knowledge does not. **Checking is cheap, so check.** One read-only call ⟨409 unchanged words⟩
Changed the definition of "get_library"
⟨187 unchanged words⟩ (`"Kayak"`, `"newegg.com"`). If the caller gave only a URL, use its hostname as the query — the query parameters identify a page state, not the company. A phrase in the user's own words is ⟨250 unchanged words⟩
⟨45 unchanged words⟩ one (\"Kayak\", \"newegg.com\"). If they supplied only a URL, pass its hostname rather than the full URL or its query parameters. A rough guess is always safe: a value ⟨20 unchanged words⟩
First tool surface recorded: 4 tools (server version 8.117.0)
Showing the latest 32 events. The API returns up to 500 and filters by kind: ?kind=tool_surface_changed
⟨173 unchanged words⟩ always safe: a value that matches nothingreturnssaysthesoone-lineandindexwhat to try next rather thanan errorerroring, andsoomittingdoesitomittingreturnsit.the one-line index.","type":"string"}},"type":"object"}
⟨217 unchanged words⟩ `needs_user`: a site needs your user signedin. It isin; not afailure:failure.giveGive them `meta.handoff.url`,saynamewhichthe sites,andthenwait.callWhen`wait_for_sign_in`theywithare`meta.handoff.ref`.doneOn `done`, send the SAME script unchanged. • `error`: ⟨26 unchanged words⟩
Wait for the user to finish signing in — Wait until the user finishes a sign-in link, so you know the moment they are done without asking them. Pass `ref`: `meta.handoff.ref` from a paused run, or `ref` from `add_connection`. **Call it right after you give the user the link, in the same turn.** Write the link in your reply first, so they can see it while this waits. It holds for up to `waitMs` (default 45000, at most 50000) and answers the moment the sign-in finishes. It returns `state`: • `done`: every site on the link is signed in. Send the SAME script again now (or carry on with the task after `add_connection`). Do not ask the …
⟨164 unchanged words⟩ report: follow the instructions that come with it. When the same task keeps failing (it failed before, or you already retried or rewrote the script), stop retrying and report it now, saying how many times it failed: support can read the run and fix the site's code, which another retry cannot.
Get the user a link to sign Bowmark in to a site — Get a sign-in link for one site, so the user can sign Bowmark in to it right now, before any run needs it. Pass the site as a domain (`youtube.com`), a URL, or a provider id. **Call this whenever the user asks to log in, sign in, connect or link an account on a site** — never send them to the Bowmark Link extension or the dashboard to do it themselves. It returns `url`, a single-use link that expires (`expiresAt`). Give the user that URL and say which site it is for. On that page they sign in with Bowmark's own browser, or, in desktop Chrome with Bowmark Link installed, press "Use Chrome ext…
⟨5 unchanged words⟩ , or incomplete so Bowmark can build it. Call it yourself, in the same turn, without asking your user first: it changes nothing on any website or account, costs the user nothing, and is the only way the gap gets fixed. Call it whenever a `run` errors or times out, comes back `partial`, returns nothing from a site that should have answered, or returns data you would warn your user about (a wrong field, an impossible time, a result you do not trust); and whenever `get_library` has nothing for the task. Pass the `runId` returned by `run` when this ⟨6 unchanged words⟩ it when `get_library` did not cover the task. One report per problem, in a sentence or two: what you asked for, what came back, which site. This records feedback only and does not retry a run.
**Executes the task on the real websites**(the search, the price check, the availability lookup, the configurator, the booking flow)and returnswhat came back. Runs a script you authored againstthe`get_library` vocabulary, on the live sites, and returns `{ ok, result, logs, error, ms }`.result. Call `get_library` FIRST— it givesfor the exact function names,argumentand shapes,and return types; this description isthenthesendLANGUAGEa+scripthow-toas `run(get_library is just{thescriptvocabulary}).`. THELANGUAGESCRIPT—is a plain async JavaScript: •`bowmark`body,isnot aready global (nowrappingimport).function.Call`bowmark`capabilitiesisoffaitready—global: `awaitbowmark.<capability>.<method>bowmark.<capability>.<fn>(...)`— always `await`,they're async. • Individual sites areorcallableonetoo,siteatvia `await bowmark.providers.<provider>.<fn>(...)`.Use one`return`whenwhat youspecificallywantTHAT site; otherwise prefer the capability, which fans out across sites and routes around failures. • Real control flow: `await`, `if`, loops, array methods (`map`/`filter`/`sort`/`slice`), and `Promise.all` for fan-out. • `return` a value to get it back (JSON-serialized).back; `log(...)` forprogress lines. • Standard JavaScript built-ins are there (`JSON`, `Math`, `Date`, `RegExp`, `Intl`,progress.`Promise`),Built-ins plus`URL` and `URLSearchParams` — use them to resolve a relative link against the page it came from and to build query strings. Nothing else from the Web platform exists: no `fetch`, `setTimeout`, `TextEncoder` or `crypto`. • `bowmark` is the ONLY I/O —`URL`/`URLSearchParams`noexist; `fetch`,`process`, filesystem, or `import`/`require`. Write a plain async body, not a wrapping function. • `bowmark.files` keeps a file the run produces (a CSV, an image`import`,a transcript) in the caller's Bowmark account`setTimeout` andgives youalink to it: `save({ name, text | base64, contentType? })` → `{ id, url, expiresAt }` (25 MB from a script), `list()`, `get(id)`, `url(id, { expiresIn })`, `makePublic(id)` → `{ publicUrl }`, `makePrivate(id)`, `delete(id)`. Return the link rather than inlining the bytes — UNLESS you need to read the text yourself (a transcript you will summarize): then return the part you need, trimmed (e.g. `text.slice(0, 4000)` per item), beside the link in the SAME run. Fetching each saved link back afterwards costs one tool call per file (measured 2026-10-02: 15 transcripts, ~100s). Keep the whole result under ~25,000 characters: clients reject or spill a larger tool result (Claude Code moved a 50 KB one to a file the modelfilesystemcoulddo notopen), and the answer is lost. • Keep scripts small and deterministic—no infinite loops. Runs in a hard sandbox with CPU + memory + wall-clock limits. **Your own tool-call budget`bowmark` istighter than you'd guess, and it decides how many calls fittheinonlyoneI/O. MINDscript.**THEMostCLOCK.MCPYourclientsclienttimetimes asingletool call out at around 55 seconds, andONE ordinaryone capability call already spends 30-55 secondsof that,fanningsooutdefault tolive sites — seeONECOMPOSITIONperbelowscript.beforeNeedcallingseveral?aRunsecondthemcapabilitytogether inthe same`Promise.allSettled`script. SENDING(aIT:droppedpasslegthestillscriptcomestextback as`run({ script }`partial`)` — there is no `site` argument; the library exposes every capability under `bowmark`. A run,stillnevergoingone after~40 seconds answers `status: "running"` with its `runId` instead of letting your tool call time out: it keeps executing on Bowmark's sideanother,so call `run({ runId })` to wait for that SAME run —and neversend the`await`scriptinsideagain,awhich`for`wouldloop.doKeep thework twice. `result` is whatever you returned; `logs` are your `log()` lines in order; on a throw/timeout `ok:false` andresult`error`underis~25,000set.characters. CHECK `status` BEFORE`ok`. It`ok`is— `ok` | `error` | `partial` | `needs_user` | `running`: •(call`running`: it is still going. Call `run({ runId })` tocollect it). • `partial`waitmeansfor thescript RAN and `result` is real and usable, but some of what itSAMEcalledrun; neveranswered — soresend theresult is narrower than what you asked for. `ok` is stillscript. •`true`;`partial`:this`result` isnot a failure. `incomplete.summary` says what happened in one sentence, `incomplete.failures` names each call that threw and whatrealthebutsitenarrowersaid,thanandasked;`incomplete.degraded``incomplete` nameseach call that answered while reporting its OWN results thin. You MUST say so when you present the result: namewhatwas missed, and do not describe it as complete, exhaustive, or 'all' of anything. A `partial` you report as whole is a wrong answer,did nota slightly smaller right one. •answer.BeforeTellyouyourconcludeusera—`partial`neveriscallfinal,itcheckcomplete. `incomplete.failures[].fixable`. `fixable.fixable: true` meansYOURyourARGUMENTargument wasrejected, not the site — the error text names what that function actually takes, so re-read it in `get_library`,wrong: fixthe argumentit andrun again; that recovers the whole answer. For any other failure re-running usually returns the same thing. • When a fan-out capability (shipping, flights, hotels, cars, etc.) fails on EVERY provider, `incomplete.failures[].dropped` carries the per-provider breakdown as an array of `{ provider, fn, reason, detail }` — branch on that instead of parsing the prose in `.error`.rerun. • `needs_user`means: a site needstheyourUSERuser signedin —in.itIt isNOTnot a failureand NOT something you can fix by editing the script. `needs` lists:thegivesites;them `meta.handoff.url`is a single-use link that expires (`meta.handoff.expiresAt`). Give the user that URL, say which sitesit covers, andWAIT.wait. When theytell you they'reare done, send the SAME scriptagainunchanged.Do NOT retry before then — it will stop at the same place and cost another run. Do NOT try to log in yourself, ask them for a password, or work around it with a different site. When the site already had a saved login that stopped working, the link signs back in to THAT login and its `id` stays the same — `list_connections` shows what this account already holds, and is worth checking before a signed-in task so you reuse a login rather than asking for a new one.•Logged-in runs need a Bowmark API key on the connection; if you get `needs_user` saying so, tell the user to add one rather than retrying. ONE EXIT IP FOR THE WHOLE SCRIPT: `await bowmark.egress.pin({ ttlMinutes: 120 })` before your first call, and every call the script makes afterwards leaves from ONE address. Reach for it when a site binds a cart, a token or a sign-in to the address that made the request, and the symptom is being logged out or asked to start over between steps. It is not a dedicated IP: the exit is a shared residential peer, `shared: true` says so, and the vendor can still replace it without telling us. **Pin FIRST** — a site you already called keeps the address it used, and the result says `pinnedAfterCalls: true` when you left it too late. Up to 7 days; anything longer comes back clamped, and the `ttlMinutes` you get back is the one that was granted. A DEDICATED IP, IF `pin` ISN'T ENOUGH: `pin` is free and shared — a stranger's device, gone in at most 7 days. `bowmark.egress.lease` is the other product`error`:a static IP nobody else is ever served, ordered for a real term and charged to the account, for a site whose session dies even on a pinned exit. Reach `bowmark.egress.quote({ country })` first — it returns your own price for that country's real availability, read live; never assume a term or a number. `lease({ country, idempotencyKey })` places the order (`idempotencyKey` is yours to choose, so a retried call cannot buy two); it is STATIC ISP ONLY today, real money, andthere is norefund — confirm with yourresult;user`error`beforesayscallingwhy. Savingitfiles,the way you would before any other purchase. `list()` and `get(id)` read what the account already holds; `use(id)` routes this run's later calls throughonespecific lease; `release(id)` stops auto-renewal (theexit IPkeeps working for its already-paid term,then goes away for good). SAVED SECRETS: `savedSecrets` lists every secret the run stored in the user's Bowmark account, each as `{ name, type, viewUrl }`. An API key a key-making function returns issavedthere automatically as `<vendor>_api_key`secrets,and later runs that call that vendor use it with nothing to configure. When `savedSecrets` is present`warnings`,tell the user what was saved and give them each `viewUrl``emptyHanded`,where they can view or manage it. `trace` is the execution trace — every capability you calledand theproviders it fanned out to underbilledthe`browser_agent`/`delegate`hoodfallbacks:`[`get_library({kind:'capability', capabilityquery:'flights',method:'search',"runmsguide" }, { kind:'provider', capability:'fli)`.
⟨903 unchanged words⟩ or work around it with a different site. When the site already had a saved login that stopped working, the link signs back in to THAT login and its `id` stays the same — `list_connections` shows what this account already holds, and is worth checking before a signed-in task so you reuse a login rather than asking for a new one. • Logged-in runs need a Bowmark API key ⟨422 unchanged words⟩ , ms }, { kind:'provider', capability:'flights', provider:'google_flights', fn:'search', results, status, ms }, …]`. The script never visits websites — it calls capabilities that route to providers, and the trace is the receipt. COMPOSITION MEANS PARALLEL, NOT SEQUENTIAL, AND THAT GOES FOR PROVIDER CALLS TOO — an `await` inside'fli
{"destructiveHint":truefalse,"idempotentHint":true,"openWorldHint":false,"readOnlyHint":falsetrue}
⟨93 unchanged words⟩ do, at https://bowmark.ai/dashboard/connections — `delete_connection` onlyforgetsasks the user to delete one this account already holds.
⟨78 unchanged words⟩ site. `delete_connection` is the other action: itforgetshands the user a link to forget the entry entirely,andwhich leaves the site session alive.
{"additionalProperties":false,"properties":{"runId":{"description":"Only to collect a run an earlier call answered `status: \"running\"` for: pass its `runId` and no `script`, and this call waits for that same run's result.","format":"uuid","type":"string"},"script":{"description":"The JavaScript script body ⟨29 unchanged words⟩ warnings };`","type":"string"}},"required":["script"],"type":"object"}
⟨116 unchanged words⟩ ["ok","error","partial","needs_user","running"],"type":"string"}},"required": ⟨3 unchanged words⟩
⟨879 unchanged words⟩ get back is the one that was granted. A DEDICATED IP, IF `pin` ISN'T ENOUGH: `pin` is free and shared — a stranger's device, gone in at most 7 days. `bowmark.egress.lease` is the other product: a static IP nobody else is ever served, ordered for a real term and charged to the account, for a site whose session dies even on a pinned exit. Reach `bowmark.egress.quote({ country })` first — it returns your own price for that country's real availability, read live; never assume a term or a number. `lease({ country, idempotencyKey })` places the order (`idempotencyKey` is yours to choose, so a retried call cannot buy two); it is STATIC ISP ONLY today, real money, and there is no refund — confirm with your user before calling it, the way you would before any other purchase. `list()` and `get(id)` read what the account already holds; `use(id)` routes this run's later calls through one specific lease; `release(id)` stops auto-renewal (the IP keeps working for its already-paid term, then goes away for good). SAVED SECRETS: `savedSecrets` lists every secret the run ⟨313 unchanged words⟩ you can tell the runs apart). Seethe `get_library` examples for the exact shape. If even one call will not fit your budget, narrow the query (fewer dates, a single site instead of a fan-out) or split the work across separate turns — do not compose more into one script to make it fit. A FAN-OUT OF FIVE THAT LOSES ONE SHOULD STILL HAND BACK FOUR: `Promise.all` rejects the whole thing the moment any leg does, so one refused request turns a finished answer into nothing. Use `Promise.allSettled` and keep the fulfilled legs. That is safe HERE and nowhere else, because Bowmark reports the dropped leg for you — the runtime counts what your script CALLED at the dispatch point, not what it chose to report, so a swallowed failure still comes back as `status: 'partial'` with the call named in `incomplete`. Say so when you present the result. SOME capabilities return their rows alongside a `warnings` array — `{ flights, warnings }`, `{ hotels, warnings }`, `{ cars, warnings }`. Others return a bare array. Thth
Forget one of the user's saved logins — Forget a saved login by the `id` `list_connections` returned. This deletes Bowmark's own record and cookies for it — it does NOT sign the account out on the site, and it cannot be undone; the user signs in again to get a working connection back. Only call this when the user asked to remove a saved login. To sign out of a site, use `logout_connection` instead. Never call it to "fix" a connection that is merely stale — `needs_reauth`/`expired` recover with a new sign-in, not a delete.
Sign one of the user's saved logins out — Sign a saved login out by the `id` `list_connections` returned. Bowmark drops the cookies it held, and where the site supports it (`siteLogout: "supported"` in the reply) the session is ended ON THE SITE too — `siteSignedOut: true` means Bowmark checked the site no longer accepts it. The saved login is KEPT, status `logged_out`, so the user can sign back in to the same entry later. Call this when the user asks to sign out of a site. `delete_connection` is the other action: it forgets the entry entirely and leaves the site session alive.
Create a named, empty slot for acredentialsecret and get back a link the user opens ⟨58 unchanged words⟩ API key or one-time code to you.** Acredentialsecret in this conversation is in your context, ⟨27 unchanged words⟩ open and the row they see in theircredentialsecret list months later, so make it `<site>_<what ⟨62 unchanged words⟩
⟨806 unchanged words⟩ back is the one that was granted. SAVEDCREDENTIALSSECRETS: `savedSecrets` lists everycredentialsecret the run stored in the user's Bowmark account ⟨544 unchanged words⟩ `warnings` from what you return does not hideiit — the
See which sites Bowmark is already signed in to — List the saved logins this account holds — which site, which account, whether it is still good, and when it was last used. A live one means a script can reach that site's signed-in pages with no sign-in step and usually no browser. Useful before a task that needs an account: a connection marked `needs_reauth` or `expired` is why a run would pause and ask the user to sign in again. Revoking one is deliberately not a tool — it is the user's to do, at https://bowmark.ai/dashboard/connections.
See which of the user's logins Bowmark already holds — List the stored credentials this account holds, by NAME. Returns name, type, which hosts each may be used against, when it expires and when it was last used — never a value, and no Bowmark endpoint returns one. **Call this BEFORE `request_secret`.** A secret the user has already set is ready to use; asking for it again sends them to a page for nothing. In a script, refer to one by name: `bowmark.secret('acme_pw')`.
Ask the user to set a login Bowmark will need — Create a named, empty slot for a credential and get back a link the user opens to fill it in. They type the value on a Bowmark page and choose how long it lives, from 5 minutes to never. It is encrypted in their own browser before it leaves, so nothing on the way — this connection included — ever sees it. **Give the user the link. Never ask them to type a password, API key or one-time code to you.** A credential in this conversation is in your context, in the transcript and in the logs. **Name it for the person, not for your script.** The name you pass is the heading on the page they open a…