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.
Changed the definition of "writ_browser_save", "writ_crawl_site", "writ_list_workflows" and 1 more
⟨10 unchanged words⟩ on purpose.","type":"boolean"},"contribute_to_catalog":{"type":"boolean"},"description":{"type":"string"},"keep_open" ⟨19 unchanged words⟩
⟨54 unchanged words⟩ ,"type":"string"},"extract_schema":{"description":"With extract_mode=schema: CSS {row_selector, fields: [{name, selector, attr?}]}, not a JSON Schema.","type":"object"},"include_paths":{"items" ⟨150 unchanged words⟩
⟨13 unchanged words⟩ live browsing. Returns id, name, declared inputs (with `input_rules`: the values and format an input accepts), schedule, and whether it is pinned as its own run_<name> tool.
⟨9 unchanged words⟩ , up to 20 pages in parallel (urls, on one site or several), or a listing plus its top N ⟨39 unchanged words⟩ bot wall or 403 (an automatic residentialretryretry; render_mode='http' opens no browser, so it never passes a check), or behind the user's sign-in (persona_id). Discussion threads keep their comments. format='html' returns the raw HTML forselectors.selectors, for one page or a batch. A page that cannot be read says why (error.code, e.g. scrape_not_found: the URL does not exist on the site) and gives retry_with when another read can help. NOT FOR: a site-wide dataset (writ_crawl_site) ⟨6 unchanged words⟩
⟨7 unchanged words⟩ (default) | html (raw HTML) |both.both; with url, urls or top_n.","enum":["markdown","html"," ⟨30 unchanged words⟩ HTTP, a browser when the page needs JS or meets a check) | http (no browser) | browser.","type":"string"},"residential_country
Authorization not required, issuer https://api.usewrit.app
Changed the definition of "writ_browser_act", "writ_browser_compose" and "writ_update_workflow"
⟨56 unchanged words⟩ read_script. RECORD: extract {variable,script}, api_call (a flow is built step by step with writ_browser_compose add_flow_step), login_post, probe_write, confirm_write. 2FA: twofa { ⟨27 unchanged words⟩
⟨10 unchanged words⟩ : named API functions (define_function with a `flow`,(preferred in an API build),fromor
Changed the definition of "writ_browser_context", "writ_browser_network", "writ_browser_save" and 27 more
{"properties":{"max_chars":{"type":"integer"},"offset":{"minimum":0,"type":"integer"},"section":{"description":"page (default) | ⟨22 unchanged words⟩
⟨6 unchanged words⟩ "minimum":0,"type":"integer"},"max_chars":{"type":"integer"},"method":{"description":"HTTP method filter.","type":"string"},"offset":{"minimum":0,"type":"integer"},"operation":{"description":"search ( ⟨21 unchanged words⟩
Authorization not required, issuer https://api.usewrit.app
Changed the definition of "writ_browser_save" and "writ_update_workflow"
Saves the open session as a replayable workflow and closes thebrowser.browser, in the place its browser ran: the user's desktop library or Writ Cloud. Composed functions become callable API functions, declared ⟨42 unchanged words⟩
⟨17 unchanged words⟩ ,"name":{"type":"string"},"save_to":{"enum":["desktop","cloud","both"],"type":"string"},"session_id":{"type":"string"}},"required ⟨3 unchanged words⟩
Added "writ_browser_live_view"; changed the definition of "writ_browser_ask_user", "writ_browser_compose", "writ_browser_network" and 4 more (40 tools before, 41 now)
⟨27 unchanged words⟩ it; the code never reaches this conversation), 'sign_in' (they sign in on the live page; no persona applies) or 'question' (their text answer).HoldsThe call that opens the ask returns at once with user_link + tell_user to relay; later calls hold up to 60s;sanswersand answer answered, waiting_for_user (the same call again) or expired. Their answer is kept if your turn ends first.
Certificate recorded, valid to 2027-01-03
Authorization not required, issuer https://api.usewrit.app
Added "writ_help"; changed the definition of "writ_browser_act", "writ_browser_ask_user", "writ_browser_cancel" and 36 more (39 tools before, 40 now)
Runs one batch of actions on an openbrowsersession and returns the fresh page.The caller is the session's brain: Writ runs no model here, and a batch performs exactly the navigations, clicks, fills, captures, probes and scripts it carries. It also composes, for clients without writ_browser_compose: actions=[{action:'define_function', name:'feed.list', from_index:3, ...}] orEach[{action:'compose',operation, payload}]; a batch mixing these with page actionsisrefused. Actions placed after a navigate, click or select that changes the page run against a page the caller has not seen yet. RECORDING RULES: {{name}} asanaction value (select/fill/type_text valueobject,navigate url) with the real value in `inputs`e.g.(
Unknown → Live
First tool surface recorded: 39 tools (server version 1.0.0)
npm:writ-mcp (package_npm) — from mcp_registry, with the record
https://api.usewrit.app/mcp (mcp_streamable_http) — from mcp_registry, with the record
Showing the latest 14 events. The API returns up to 500 and filters by kind: ?kind=tool_surface_changed
{"properties":{"operation":{"description":"list | define_functionThe|action;compile_functionadd_flow_step,|replace_flow_steptest_functionand|remove_flow_stepremove_functionchange|oneadd_stepsstep|ofremove_stepa|function'sset_inputs.flow.","enum":["list","define_function","compile_function","test_function","remove_function","add_flow_step","replace_flow_step","remove_flow_step","add_steps","remove_step","set_inputs"],"type":"string"},"payload":{"description":"The operation'sarguments.arguments; add_flow_step: {function, step, at?}, remove_flow_step: {function, at}.","type":"object"},"session_id":{ ⟨7 unchanged words⟩
⟨15 unchanged words⟩ :"string"},"function_updates":{"description":"Targeted edits of one function:[{function_name,patchpatch|json_edits|script_edits}]|(addjson_edits/config/flow/steps/-|appendsscript_editsa step); add:{step}].adds a function.","items":{"type":"object"}, ⟨112 unchanged words⟩
⟨14 unchanged words⟩ ,"description":{"type":"string"},"keep_open":{"type":"boolean"},"name":{"type":"string"},"save_to" ⟨13 unchanged words⟩
{"properties":{"include_closed":{"type":"boolean"},"limit":{"type":"integer"}},"type":"object"}
{"properties":{"auth_mode":{"enum":["reuse","fresh_login"],"type":"string"},"execution_target":{"description":"cloud | auto | ⟨5 unchanged words⟩ app).","type":"string"},"fresh_exit":{"type":"boolean"},"goal":{"type":"string"},"persona_id":{"type":["integer","string"]},"residential_country":{"type":"string"},"url":{"type":"string"}, ⟨17 unchanged words⟩
⟨4 unchanged words⟩ "},"crawl_id":{"type":"integer"},"limit":{"type":"integer"},"runs":{"type":"integer"}},"type":"object"}
{"properties":{"allow_subdomains":{"type":"boolean"},"content_spec":{"type":"object"},"delay_ms":{"type":"integer"},"device":{"type":"string"},"exclude_paths":{"items":{"type":"string"},"type":"array"},"executor":{"description":"regular (default, no ⟨28 unchanged words⟩ each page.","type":"string"},"extract_schema":{"type":"object"},"include_paths":{"items":{"type":"string"},"type":"array"},"intent":{"description":"The section wanted, ⟨7 unchanged words⟩ and depth.","type":"string"},"limit":{"type":"integer"},"max_age":{"minimum":0,"type":"integer"},"max_concurrent_shards":{"type":"integer"},"max_depth":{"type":"integer"},"name":{"type":"string"},"ocr_mode":{"type":"string"},"output":{"type":"object"},"page_budget":{"description":"Cap on pages fetched. ⟨3 unchanged words⟩ "type":["integer","string"]},"preview_chars":{"type":"integer"},"rank_cap":{"description":"Top N items of ⟨11 unchanged words⟩ "minimum":1,"type":"integer"},"relevance_threshold":{"maximum":1,"minimum":0,"type":"number"},"render_mode":{"type":"string"},"residential_country":{"type":"string"},"respect_robots":{"type":"boolean"},"same_domain":{"type":"boolean"},"save_as":{"type":"string"},"seed_urls" ⟨10 unchanged words⟩ :"string"},"type":"array"},"shard_size":{"type":"integer"},"speed":{"type":"string"},"timeout_seconds":{"type":"integer"},"url":{"type":"string"},"use_residential":{"type":"boolean"},"wait":{"description":"Hold until ⟨16 unchanged words⟩
⟨4 unchanged words⟩ ,"limit":{"type":"integer"},"output":{"type":"object"},"preview_chars":{"type":"integer"},"timeout_seconds":{"type":"integer"},"wait":{"description":"Hold until done and ⟨9 unchanged words⟩
{"properties":{"ai_entry_url":{"type":"string"},"ai_prompt":{"description":"Wake an AI agent with this task.","type":"string"},"ai_session_id":{"type":"integer"},"blocks":{"items":{"type":"object"},"type":"array"},"channels":{"description":"Notification channels.","items":{"type":"string"},"type":"array"},"cooldown_minutes":{"type":"integer"},"description":{"type":"string"},"enabled":{"type":"boolean"},"inputs":{"type":"object"},"name":{"type":"string"},"notify" ⟨13 unchanged words⟩ workflow_* events.","type":"string"},"on_workflow_id":{"type":"integer"},"priority":{"type":"integer"},"recipients":{"items":{"type":"string"},"type":"array"},"run_function":{"type":"string"},"run_functions":{"items":{"type":"string"},"type":"array"},"run_workflow":{"description":"Workflow to run.","type":"string"},"run_workflow_id":{"type":"integer"},"schedule":{"description":"when='scheduled': {kind, ⟨9 unchanged words⟩ ":{"description":"change_detected: the monitor_id.","type":"integer"},"target_selector_id":{"type":"integer"},"title":{"type":"string"},"webhook_trigger_id":{"type":"integer"},"when":{"description ⟨27 unchanged words⟩
⟨6 unchanged words⟩ true writes.","type":"boolean"},"desired_inputs":{"items":{"type":"string"},"type":"array"},"goal":{"type":"string"},"request_samples":{"items":{"type":"object"},"type":"array"},"requirements":{"type":"string"},"response_sample":{"type":"string"},"step_index":{"type":"integer"},"workflow":{"type":"string"},"workflow_id": ⟨6 unchanged words⟩
{"properties":{"device":{"type":"string"},"enabled":{"type":"boolean"},"extract":{"description":"Browserless read instead of ⟨29 unchanged words⟩ "type":["string","integer"]},"interval_minutes":{"type":"integer"},"mode":{"enum":["selector","visual"],"type":"string"},"persona_id":{"type":["integer","string"]},"request_url":{"type":"string"},"requires_browser":{"type":"boolean"},"residential_country":{"type":"string"},"selector":{"description":"CSS selector whose text ⟨11 unchanged words⟩ ,"session_id":{"type":"string"},"try_anyway":{"type":"boolean"},"url":{"type":"string"},"use_residential" ⟨24 unchanged words⟩ ","enum":["content","price"],"type":"string"},"zone_text":{"type":"string"}},"required":[" ⟨3 unchanged words⟩
{"properties":{"task_id":{"type":"integer"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"}},"type":"object"}
⟨5 unchanged words⟩ | json.","type":"string"},"q":{"type":"string"},"view":{"type":"string"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"}},"type":"object"}
{"properties":{"label":{"type":"string"},"timeout_seconds":{"maximum":300,"minimum":5,"type":"integer"},"wait_timeout":{"maximum":300,"minimum":5,"type":"integer"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"}},"type":"object"}
⟨17 unchanged words⟩ ,"list"],"type":"string"},"automation_id":{"type":"integer"},"commit_selector":{"description":"checkout: the place-order ⟨3 unchanged words⟩ ,"confirmation_id":{"type":"string"},"currency":{"type":"string"},"fields":{"type":"object"},"grant_id":{"type":"string"},"max_amount" ⟨4 unchanged words⟩ may charge.","type":"number"},"payment_method_id":{"type":"string"},"purpose":{"description":"One line the user ⟨15 unchanged words⟩ {"description":"request: the store's domain.","type":"string"},"steps":{"items":{"type":"object"},"type":"array"},"summary":{"type":"string"},"total_selector":{"type":"string"}},"required":[" ⟨3 unchanged words⟩
⟨21 unchanged words⟩ ,"record_login"],"type":"string"},"device":{"type":"string"},"domain":{"description":"list: personas usable on this site. request: the site.","type":"string"},"execution_target":{"enum":["cloud"],"type":"string"},"force":{"type":"boolean"},"include_runs":{"type":"boolean"},"link_id":{"description":"wait: the link_id of a persona_needed / request answer.","type":"string"},"login_url":{"type":"string"},"persona_id":{"type":"integer"},"wait":{"type":"boolean"},"why":{"description":"request: ⟨14 unchanged words⟩
{"properties":{"pinned":{"type":"boolean"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"}},"type":"object"}
{"properties":{"auth_mode":{"enum":["reuse","fresh_login"],"type":"string"},"device":{"type":"string"},"fresh_exit":{"type":"boolean"},"goal":{"type":"string"},"persona_id":{"type":["integer","string"]},"residential_country":{"type":"string"},"skip_existing":{"type":"boolean"},"skip_marketplace":{"type":"boolean"},"url":{"type":"string"}, ⟨14 unchanged words⟩
{"properties":{"crawl":{"type":"string"},"limit":{"type":"integer"},"max_age":{"description":"Reuse a run younger than this (seconds).","minimum":0,"type":"integer"},"output":{"type":"object"},"preview_chars":{"type":"integer"},"timeout_seconds":{"type":"integer"},"wait":{"description ⟨9 unchanged words⟩
{"properties":{"device":{"type":"string"},"execution_target":{"description":"cloud | auto | ⟨5 unchanged words⟩ app).","type":"string"},"files":{"type":"object"},"function_name":{"description":"One function of a multi-function workflow.","type":"string"},"function_names":{"items":{"type":"string"},"type":"array"},"inputs":{"description":"Run inputs (top-level ⟨39 unchanged words⟩ ,"private_test"],"type":"string"},"output":{"type":"object"},"persona_id":{"description":"Run as this saved identity.","type":["integer","string"]},"residential_country":{"type":"string"},"timeout_seconds":{"type":"integer"},"use_residential":{"type":"boolean"},"wait":{"type":"boolean"},"workflow":{"description":"Workflow name ⟨24 unchanged words⟩
⟨4 unchanged words⟩ "},"limit":{"type":"integer"},"output":{"type":"object"},"preview_chars":{"type":"integer"}},"required":["crawl"],"type":"object"}
{"properties":{"device":{"type":"string"},"format":{"description":"markdown (default) | ⟨8 unchanged words⟩ ,"both"],"type":"string"},"include_paths":{"items":{"type":"string"},"type":"array"},"persona_id":{"description":"Read signed in as ⟨5 unchanged words⟩ "type":["integer","string"]},"preview_chars":{"type":"integer"},"render_mode":{"description":"auto (default: HTTP ⟨7 unchanged words⟩ | http | browser.","type":"string"},"residential_country":{"type":"string"},"respect_robots":{"type":"boolean"},"top_n":{"description":"With url ⟨54 unchanged words⟩
{"properties":{"device":{"type":"string"},"limit":{"type":"integer"},"preview_chars":{"type":"integer"},"q":{"type":"string"},"workflow":{"type":"string"},"workflow_id": ⟨6 unchanged words⟩
⟨9 unchanged words⟩ :"integer"},"type":"array"},"enabled":{"type":"boolean"},"every_minutes":{"description":"Interval in minutes.","type":"integer"},"function":{"type":"string"},"functions":{"items":{"type":"string"},"type":"array"},"inputs":{"type":"object"},"kind":{"description":"interval | daily | ⟨11 unchanged words⟩ ,"tz":{"description":"IANA timezone.","type":"string"},"workflow":{"type":"string"},"workflow_id":{"type ⟨3 unchanged words⟩
{"properties":{"crawl":{"type":"string"},"default_max_age_seconds":{"minimum":0,"type":"integer"},"description":{"type":"string"},"name":{"type":"string"},"settings":{"description":"Sparse settings patch.", ⟨6 unchanged words⟩
⟨4 unchanged words⟩ ,"desktop"],"type":"string"},"device":{"type":"string"},"expected_updated_at":{"type":"string"},"function_name":{"type":"string"},"function_updates":{"description":"Targeted edits of one function: [{function_name, patch | json_edits | script_edits}].","items":{"type":"object"},"maxItems":50,"minItems":1,"type":"array"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"max_chars":{"maximum":20000,"minimum":1000,"type":"integer"},"offset":{"minimum":0,"type":"integer"},"operator":{"type":"string"},"patch":{"description":"Settings and metadata to change; {\"name\": \"New name\"} renames it.","type":"object"},"path":{"type":"string"},"regenerate_skill":{"type":"boolean"},"replace_steps":{"items":{"type":"object"},"type":"array"},"section":{"enum":["source","flow","contract"],"type":"string"},"step_id":{"type":"string"},"step_updates":{"description":"[{index, patch}] merged into those recorded steps.","items":{"type":"object"},"type":"array"},"validate_only":{"description":"true previews and validates; nothing is saved.","type":"boolean"},"verbose":{"type":"boolean"},"workflow":{"description":"Name or id; local:<n> for a desktop workflow.","type":"string"},"workflow_id":{"type":"integer"}},"type":"object"}
{"properties":{"ai_supervise":{"type":"boolean"},"anonymous":{"type":"boolean"},"build_id":{"type":"integer"},"device":{"type":"string"},"execution_target":{"type":"string"},"goal":{"type":"string"},"level":{"enum":["light","deep"],"type":"string"},"mode":{"description":"guided (default: the ⟨29 unchanged words⟩ ,"persona_id":{"type":"integer"},"residential_country":{"type":"string"},"respect_robots":{"type":"boolean"},"save_as":{"type":"string"},"scope":{"type":"string"},"skip_existing":{"description":"Skip the proposal of ⟨14 unchanged words⟩ "boolean"},"url":{"type":"string"},"use_residential":{"type":"boolean"}},"type":"object"}
⟨11 unchanged words⟩ ,"ai_task"],"type":"string"},"ai_session_id":{"type":"integer"},"buy":{"description":"Run the workflow or ⟨25 unchanged words⟩ :"string"},"type":"array"},"cooldown_minutes":{"type":"integer"},"device":{"type":"string"},"enabled":{"type":"boolean"},"entry_url":{"type":"string"},"max_steps":{"type":"integer"},"message":{"type":"string"},"monitor_id":{"type":"integer"},"name":{"type":"string"},"prompt":{"description":"action='ai_task': what the ⟨50 unchanged words⟩ ,"gte"],"type":"string"},"title":{"type":"string"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"}},"required":["monitor_id","action" ⟨2 unchanged words⟩
{"properties":{"device":{"type":"string"},"limit":{"type":"integer"},"output":{"type":"object"},"preview_chars":{"type":"integer"},"q":{"type":"string"},"refs" ⟨10 unchanged words⟩ "array"},"run_id":{"type":"integer"},"view":{"type":"string"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"} ⟨2 unchanged words⟩
{"properties":{"device":{"type":"string"},"limit":{"type":"integer"},"status":{"description":"Filter by status.","type":"string"},"workflow":{"type":"string"},"workflow_id":{"type ⟨3 unchanged words⟩
⟨12 unchanged words⟩ function or step edits, a settings patch. copy_to keeps a linked copy on Writ Cloud or the user's desktop. Every option: writ_help('writ_update_workflow').
{"properties":{"copy_to":{"enum":["cloud","desktop"],"type":"string"},"workflow_id":{"type":"integer"}},"type":"object"}
⟨7 unchanged words⟩ beyond a recording: named API functions (define_function with a `flow` (preferred in an API build), from a captured call's index, or a listspayload,payload; live-tested as defined; compile_function for an authenticated request ⟨11 unchanged words⟩
⟨15 unchanged words⟩ made after capture_network: the site's real API.Anwrit_browser_act api_call probes one with other values (a `flow`: several requests); an index feeds writ_browser_compose define_function from_index.
⟨109 unchanged words⟩ 2FA rules. NOT FOR: reading pages (writ_scrape)or building.anAn API:(writ_website_to_api).opens this same browser, bound to a build.
⟨45 unchanged words⟩ 'recording' / '2fa' for the browser rules, 'http_flows' / 'flow_operators' for writing an HTTP flow, or empty for the list of topics.
⟨5 unchanged words⟩ name, 'guide', 'actions', 'recording', '2fa', 'http_flows', 'flow_operators', or empty.","type":"string"}},"type":"object"}
⟨29 unchanged words⟩ writ_browser_save. A goal that asks for an APIgoesopenstoaswrit_website_to_api'sa writ_website_to_api buildinstead.instead: the same browser, bound to the build.
⟨6 unchanged words⟩ API (named functions with inputs, returning JSON)for a service with no practical,officialbuiltAPI.by3thecalls:caller(1)inthisatoolrealwithbrowser. url = the page that already shows the rows (asearchsearch-resultsresults orlistingaURLlisting, not the home page)and; goal = the inputs and fieldswanted; (2)wanted.writ_discovery_status(build_id,Thewait=true);call(3)firstonproposessuccess,theitsuser'srun_examplematching(writ_run_workflowsavedwithworkflowsfunction_name).andWritmarketplacebuildsAPIsit(skip_existingitself/(anskip_marketplaceAIbuildfastfresh),paththeninopens arealcloud browser,eachboundfunctiontolive-tested)theandbuild:firstcapture_networkproposesrecords theuser's matchingsite'ssavedownworkflowscalls,andwrit_browser_networkmarketplacereadsAPIsthem,(skip_existingapi_call/probesskip_marketplacethem;buildafresh).failedAprobesign-iniswallevidence,answersnotpersona_needed.theNOTend.FORFunctions:readingHTTPcontentflows first (writ_scrape)writ_browser_composeordefine_functiona`flow`;one-offwrit_help('http_flows')),tasksingle requests (writ_browser_usefrom_index).Modesmixedandin,theDOMguidedextractionbuildlast. writ_browser_save, then run_example on two inputs. mode=intelligent:writ_helpWrit builds it alone ('writ_website_to_api'+ writ_discovery_status wait=true)., on request.
⟨6 unchanged words⟩ :"string"},"mode":{"description":"intelligentguided (default,:Writthebuildscalleritdrives) |guidedintelligent (theWritcallerbuildsdrivesit alone, on request) | auto | fast | crawl | browser.","enum":["intelligentguided","guidedintelligent","auto","fast","crawl","browser ⟨36 unchanged words⟩
Live browser view — The live browser card's own access: the card ChatGPT and Claude show under writ_browser_use and writ_record_website calls it to read the session's state and get one-time passes for the session's screencast and its take-over channel. The user watches the page in the card and can take it over there. It is not part of a conversation's work.
⟨19 unchanged words⟩ ,"type":"array"},"inputs":{"description":"Values held server-side for {{placeholder}} substitution, e.g. {\"city\":\"Paris\"}. Secrets here or on a fill's data_key stay out of the recorded steps.","type":"object"},"max_chars":{"description":"Clip of the returnedpage_dom / page_textpage (default 40000, ≤200000).A get_dom probe on a real app is 500KB; evaluate_js / inspect / read_text return just the target.","type":"integer"},"session_id":{"description":"Session id from the start tool.","type":"string"}},"required":[" ⟨4 unchanged words⟩
Asks the Writ user (the person who ownsHandsthisanWritopenaccount)session tostep in ontheanWritopenuser:browserkind='captcha'session:(they complete a security check(CAPTCHA, 'confirm it's you', Arkose/hCaptcha/reCAPTCHA puzzle)in the live browser, supply a one-time 2FA code),or answer a question only they can decide. For: writ_browser_act returning security_check with auto_solved false; a `twofa` action failing with twofa_mint_failed or twofa_no_persona'twofa' (kind='twofa'); any point where only a human cantheyproceed.supplyAthe one-time codeis NOT a first-resort ask: twofa_method_mismatchandtwofa_verify_method mean the page is on a method other than the persona's, which the page itself switches or resends, as the message says. With kind='twofa' theWritusertypespastesit; the codein the Writ app and Writ enters it server-side, so itnever reachesyou; kind='question' returns the user'sthistextconversation),so a code sent that way would reach the conversation. The sessionorpauses'question' (the user is notified in the app and by email and controls thetheirlivetextpageanswer); this call.holdsHolds up to60s and returns status 'answered' (with solved / answer, or60`entered`s;foranswerstwofa)answered,'waiting_for_user'waiting_for_user (the same callwith the same session_id keeps waiting while the user holds the pageagain),or'expired'.expired.
{"properties":{"kind":{"description":"captcha:the user completes a check in the live browser. question: the user answers in text.| twofa:the user supplies the one-time code the page is asking for; Writ types it server-side and returns `entered`, never the|code.question.","enum":["captcha","question","twofa"],"type":"string"},"question":{"description":"The question, inkind='question': one shortsentence (required for kind 'question').sentence.","type":"string"},"session_id":{ ⟨3 unchanged words⟩ description":"Hold up to this long (1-60, default 60).","type":"integer"}},"required ⟨3 unchanged words⟩
Closes an open browsersession. For: a finished task the user does not want to reuse, orsession (anabandoned session. Anopen cloud browserkeeps consuming execution timebills until it isclosed.closed). Unsaved workis kept: a session with defined functions, or a recordingthat did more than visit pages,is auto-savedas a workflowfirst(and the reply names it;an inactive draft when it would not replay).discard=truecloses without keeping anything. A session bound to a build is settled by that auto-save, or markedkeepscancelled.nothing.
{"properties":{"discard":{"description":"true = theThrowsession'saway unsaved stepsand functions are thrown awayinstead ofauto-saved. Default false.auto-saving.","type":"boolean"},"session_id":{ ⟨6 unchanged words⟩
Authors the workflowbeing builtin an openbrowsersession:named functions, inputs and explicit steps that make what the session drove a real, complex, callable workflow rather than a replay of clicks. WHEN IT IS NEEDED:beyond aplainrecordingneeds none of it (there a caller input is a {{name}} value + `inputs` on writ_browser_act, and the data is an `extract` action). It exposes: namedfunctions (anAPI),gives an input a description/default, and adds steps the recorder cannot see. OPERATIONS: define_function {name, fn_type api|list|script|extraction, ...} · compile_function {name, from_index}: deterministic (no-LLM) capture->function that traces session tokens to an is_auth bootstrap, generates per-call ids ({{uuid()}}), and marks a write so the build never sends it (a real run does) · test_function {name, sample_inputs} · remove_function {name} · set_inputs {inputs:{name:{default?,description?,required?,example?}}} · add_steps {steps:[{type,...}], at?} · remove_step {id} · list (the draft: steps, data_steps,functions,inputs, build). Fastest paths:(a) a list / table / search-results page: writ_browser_context section=lists returns a live-tested `define_function` payload, and with then_save:{name} one call here defines, tests and saves it. (b) a site endpoint: once capture_network and writ_browser_network locate the call,define_function{name:'quotes.list', from_index:<index>, request:{url:'https://site/api/quotes?page={{page}}'}, input_variables:[{name:'page',example:'1'}], response_extractions:{quotes:{from:'json',path:'quotes'},has_next:{from:'json',path:'has_next'}}, then_save:{name:'...'}}. Every function is live-tested as it is defined (an in-session request, oraDOM read, with sample_inputs={name: value}); a failed test returns feedback and keeps nothing, and a corrected definition reuses the same name. test=false skips the proof (a real run proves it later). Saved functions run through writ_run_workflow function_name. DETAILS: - define_function: a named callable the saved workflow exposes. fn_type api (backed by one of the site's endpoints: from_index=<acaptured call's indexfrom writ_browser_network> seeds method/url/headers/body from the capture; overridden request fields take {{name}} placeholders, secrets {{secret:name}}, anti-CSRF echoes {{cookie:NAME}}), scriptor(aread-only JS IIFE returning the data from the page), list (the generated-JS form for any list/table: row_selector + fields {name: sub-selector | {selector, attr}}; writ_browser_context section=lists returns thislists payloadready-made),or extraction (one selector's text). A list/script/extraction function reads the page it was defined on: page_urllive-tested asa template (https://site/search?q={{query}}), or an `example` on each input_variable from which the URL is templated. then_save:true (or {name, description}) saves the workflow the moment the function passes its live test. Names of the form <surface>.<verb> (orders.list, orders.create) group functions by surface. input_variables=[{name,description,required,example}], output_fields and response_extractions declare the fields callers pass and get back. Supported specs: JSON {from:'json',path:'data.items'}, embedded JSON {from:'embedded_json',kind:'array',has:['id']}, server HTML {from:'html_css',selector:'.row',attribute:'data-id',all:true}; with `fields` it returns row objects, which is how a server-rendered list becomes a BROWSERLESS function: {from:'html_css',selector:'tr.athing',all:true,base_url:'<page>',fields:{title:{selector:'.titleline > a'},url:{selector:'.titleline > a',attribute:'href'}}} on an `api` function that GETs the page (no browser at replay, so cheaper than a `list`/`script` function whenever the rows are in the served HTML); a field with several values per row (tags, authors) is {selector:'a.tag',all:true} (a list; without all, the first only); a has_next flag is {from:'html_css',selector:'<next link>',exists:true} (true/false, never the link text) and the next page is its href {selector:'<next link>',attribute:'href',required:false}; regex {from:'regex',pattern:'...',group:1}, header {from:'header',name:'x-next'}, body {from:'body'}, or legacy '$.json.path'. The defaultdefined;shapecompile_functionisfor anAuphan-style named graph: ordered is_auth functions publish dynamic token/id/origin values consumed as {{extracted:name}}, while each data function remains independently callable. flow={version:1,steps:[...]} is forauthenticated requestloops, recursive mapping, cross-page dedupe, cursor pagination or a composite return; it is schema-validated and live-tested at once in the current browser session with its cookies),persona and egressset_inputs,using the same interpreter as the saved HTTP laneadd_steps,and the later saved run remains the final engine=http parity proof. is_auth=true marks the sign-in function: it runs first on every replay and its response_extractions publish values the others consume as {{extracted:<name>}}. -test_function{name,sample_inputs}: proves a defined function again. -remove_function{name}. - add_steps {steps:[...],at?}: explicit replayable steps the DOM recorder cannot see (navigate, click, fill, select, press, wait, wait_for, extract, evaluate, api_call, login_post, return, upload, wait_for_download), inserted at a position (default: append). -remove_step{id}. - set_inputs {inputs:{name:{default?,description?,required?,example?}}}:the parameters a caller passes at run time. A save is refused unless every {{name}} in a step or function is a declared input, a credential, a {{cookie:}}/{{extracted:}} runtime reference, or produced by an earlier step. Credentials are never inputs: they come from the persona or a data_keylist.fill. -Payloadlistshapes:the draft so far. On writ_browser_save, api functions become api_call steps (auth first), the workflow becomes api_recorded when every step is a call, and each function is callable by namewrit_help(writ_run_workflow function_name'writ_browser_compose')and documented at GET /api/v1/workflows/{id}/api-docs..
{"properties":{"operation":{"description":"list | define_function | compile_function | test_function | remove_function | add_steps | remove_step | set_inputs.","enum":["list","define_function","compile_function", ⟨7 unchanged words⟩ ,"payload":{"description":"The operation's arguments.define_function: {name, fn_type?, description?, surface?, from_index?, request?{method,url,headers,body_template}, flow?{version,steps}, script?, selector?, input_variables?, output_fields?, response_extractions?, is_auth?, order?, sample_inputs?, test?}. compile_function {name, from_index, sibling_index?, input_variables?, response_extractions?, page_url?}: the deterministic (no-LLM) way to turn a captured authenticated request (a GraphQL/RPC POST, a form submit) into a callable function. Writ decodes the body, keeps the static parameters, traces each session-minted token (csrf/xsrf/dtsg/lsd/etc.) to where a fresh session re-reads it and emits an is_auth bootstrap that publishes it as {{extracted:}}/{{cookie:}}, replaces per-call client values (idempotence token, session id, timestamp) with runtime generators ({{uuid()}}, {{uuid(session)}}, {{timestamp_ms()}}, {{counter()}}), templates the caller inputs, and classifies a write (create/post/send/delete): the build never sends it, a real run does (mutation_mode='dry_run' previews it). It suits any authenticated mutation or token-bound endpoint better than a hand-built api function. A second capture of the same request (sibling_index, else the nearest one Writ finds) tells the pagination inputs and per-call values from stable ids: a UUID unchanged between the two is kept as captured. test_function: {name, sample_inputs?}. remove_function: {name}. add_steps: {steps:[{type, config|flat fields, description?}], at?}. remove_step: {id}. set_inputs: {inputs:{name:{default?, description?, required?, example?}}}.","type":"object"},"session_id":{"description":"Session id from the start tool.","type":"string"}},"required":[" ⟨4 unchanged words⟩
Reads context forRe-reads an openbrowser session.session: section=page (default) re-reads the live page:url,formfields, buttons,links and thecleanedDOM. section=map reads the build this session is bound to: the pages, captured calls and candidate functions Writ's earlier rungs found (evidence, not yet verifiedDOM),plus what has been composed so far. section=lists scanslists (thelive page at no AI cost: thepage's repeating rows,their field selectors, which captured request carries them, andwith a live-tested`define_function`define_function payloadthatfor writ_browser_composeaccepts as is, so a guided session opened on a results URL becomes one list/search API with that payload and a save),without probingmap (thepage by hand. section=explorer pages through Writ's full recording policy; section=concierge_api pagesevidencethroughof theAPI-builder policy. The policy sections are reference forbuildunusualtheflowssession(logins,ismulti-requestboundchainsto), not a prerequisite..
{"properties":{"max_chars":{"description":"Characters per page (1000–10000, default 8000).","type":"integer"},"offset":{"description":"Paging offset for the policy sections.","minimum":0,"type":"integer"},"section":{"description":"page (default) | lists | map | explorer |concierge_apiconcierge_api.","enum":["page","lists"," ⟨12 unchanged words⟩
Searchesor reads the requests the live page has made: the site's real backend API, as opposed to its HTML. operation=search lists matching calls(filtered by `query`operation=search,/query,`method`method);operation=detail returns oneorcallreads in fullby `index`(method, url, request headers, request bodyoperation=detail,response bodyindex).Indices are stable forthesession, so one from an earlier search still resolves later; only the oldest calls age out of the retained window, and asking for one of those says so rather than returning a different call. Calls come from the capture_network action of writ_browser_act, whichbackendreloadsrequests the pagewith capture armed; a POST (login/search/submit) is caughtmadebyafterperformingcapture_network: theaction that triggers it, thensite'scapturing.realAnyAPI.call'sAn index feeds writ_browser_compose define_functionas from_index=<index>, turning it into a callable function. Held credential values are replaced with their placeholder in the output.from_index.
{"properties":{"index":{"description":"Which call to read,detail:forwhichoperation=detail.call.","minimum":0,"type":"integer"},"max_chars":{"description":"Window size, 1000-10000 (default 8000; larger values are clamped); offset pages it.","type":"integer"},"method":{"description":"Filter byHTTPmethod.method filter.","type":"string"},"offset":{"minimum":0,"type":"integer"},"operation":{"description":"search (default) | detail.list/get are aliases.","enum":["search","detail"],"type":"string"},"query":{"description":"Substringfilter across method, url, status, and bodies.filter.","type":"string"},"session_id":{ ⟨6 unchanged words⟩
Saves the openbrowsersession as aclean,replayable workflow and closes the browser.Everything composed with writ_browser_compose is materialized: named apiComposed functions becomeapi_call steps with the authcallablefunctionAPIfirstfunctions, declared inputs becomethe workflow's parameters, explicit steps land at theiritsposition.parameters. Thesaved workflow is active immediately: it runs on demand throughanswerwrit_run_workflowcarries(function_namethecallssteps,onethefunction)inputsoranditsaownrun_example;run_<name>thetoolworkflow(writ_pin_workflow_tool)runs at zero AI cost,can be scheduled with writ_set_schedule, exposed as a REST endpoint with writ_expose_workflow_api, and documented at GETthrough/api/v1/workflows/{id}/api-docs.writ_run_workflow. Asession bound to a buildsave(writ_website_to_apithatguided)referencessettlessomethingthatnobuild.runThecouldsaveresolve is refused,with the reasons,when a step or function references something no run could resolve;and the browserthenstays open.A workflow is only as sound as the task's run on the live page.
{"properties":{"allow_no_data":{"description":"SavesSave a workflow thatyieldsreturns no data(navigation/actions only), on purpose.Off by default: a save with no data step and no defined function is refused for an API build (no define_function or extract yet) and warned for a task recording: such a workflow runs green and returns nothing.","type":"boolean"},"description":{"type":"string"},"keep_open":{"description":"Leaves the browser open after saving (default false: saving closes it).","type":"boolean"},"name":{"description":"Short workflow name (defaults to the goal).","type":"string"},"session_id":{"type ⟨5 unchanged words⟩
Lists thecloud browser sessions this account has open. Anaccount's opensession is warm, parked on its current page, and keeps billing while it stays open; its session_id resumes it in writ_browser_act / writ_browser_context without a secondcloud browserbeside itsessions (a new writ_browser_use opens one). Returnseachsession's id, status,resumableflag, current urlbyanditsgoal.session_id).
{"properties":{"include_closed":{"description":"Also lists recently closed sessions (not resumable) for reference. Default false: only open, resumable sessions.","type":"boolean"},"limit":{"description":"Max sessions to return (default 20).","type":"integer"}},"type":"object"}
A REAL CLOUD BROWSER FOR A TASK ON AWEBSITESITE: the user'sownsigned-in account (email, social, shop, bank or work portal, througha persona_id from writ_personas: the password stays sealed ⟨7 unchanged words⟩ a password), a click, form, submit,or search inside an app,setting change, bookingorpost, ora page a plain fetch cannot open (login wall, 403, CAPTCHA).OPENSa realOpenscloudthe browser and returns the firstlive page observation. It is notpage;anwrit_browser_actautonomousdrivesagent:it (Writ runs no model here, the caller is the brain and the driver, and each writ_browser_act(session_id)batch performs exactly the actions.itRecording isgiven. One call opens one browser for one task and performs no step itself. RECORDING IS ALWAYS ON, saving isalways ondemand:every interactionwrit_browser_saveinturns the sessionis recorded. A task recorded for reuse carriesintoeachavalueworkflow thatchanges as {{name}} with its real value in writ_browser_act `inputs`, and its data leaves through an `extract` action; writ_browser_save(name) then answers with the steps, inputsreplaysandwithout arun_examplebrowser,and the workflow replays at zero AI cost (writ_run_workflow).writ_browser_cancel closesan unsaved session;it (an open browser billsuntil it is closed. In the session: navigate, click, fill, type, select, press keys, scroll, switch tabs, upload a file, sign in (a persona's 2FA code is minted server-side); see the page (read_text, get_dom for the real HTML, inspect a selector, list_candidates for repeating rows, get_screenshot);.readTheeverystartbackendanswercallcarries thepage makesaction(capture_networkvocabulary,then search/read them with writ_browser_network); run any JavaScript onthelive pagerecording(evaluate_js)rules andcall the site's backend from inside the session, withits cookies (api_call); workapage whose contentpersona,onlytheappears2FAafterrules.interaction.NOT FOR: readinga page, a fewpages,or the top N items of a listing(writ_scrape: one call, 2-10s, no browser);collecting a site into a dataset (writ_crawl_site); turning a siteorintobuilding an API (writ_website_to_api, which opens the same browser bound to a build).A browser costs execution time for as long as it is open. The page comes back after every batch and on demand via writ_browser_context(section=page). A sensitive fill carries data_key: the value is held server-side and the saved step keeps a placeholder, never the raw value. writ_browser_compose adds what the recorder cannot see (named functions, explicit steps, an input's description/default). A saved workflow replays the same task without a browser (writ_list_workflows -> writ_run_workflow); the first observation lists this site's matching ones in `saved_for_this_site`. A browser's exit IP is fixed once it opens: past a bot wall or CAPTCHA, a new session with use_residential=true exits residential (writ_browser_cancel closes the blocked one). writ_browser_sessions lists open sessions; an open one is warm and cheaper to continue than a new one.
{"properties":{"auth_mode":{"description":"reuse verifies unknown saved authentication before adopting it; fresh_login starts without old cookies/storage while preserving the persona's device and network identity, the mode for recording a new login.","enum":["reuse","fresh_login"],"type":"string"},"execution_target":{"description":"Where the session runs: 'cloud'cloud(managed|cloudautofleet),|'auto'local (the user's ownlinked Writdesktop appwhen it is online, else cloud), or 'local' (only their own desktop app).On the user's own machine the session keeps their computer and IP and exposes nothing local to the cloud. No value means the account's default set in the Writ app. A 'local' run reports local_agent_offline when their app is closed; execution_target='cloud' re-runs it in the cloud, off the user's machine.","type":"string"},"fresh_exit":{"description":"Residential only: skips the persona's usual exit for this site and draws a new address, for after the site refused it (an IP rate limit, a sign-in rejected with correct credentials). Ignored on server-IP egress.","type":"boolean"},"goal":{"description":"Optional label describing the task, for the run log. A label only: the tool performs no step from it, and every step is a writ_browser_act batch.","
The originaldocuments a crawl capturedfiles (PDFs, officedocsdocuments, images, CSVs)as stored files: filename, size, version, source_url, andashort-TTL download_url fetchable with no further auth. The crawl's dataset holds the extracted text; this returns the files themselves. `crawl_id` selects one run; `crawl` (savedcrawlslug/name/id) itscaptured,mostwithrecentshort-livedcompleteddownloadrun(s).URLs.
{"properties":{"crawl":{"description":"Saved crawl slug, name, or id (alternative to crawl_id).","type":"string"},"crawl_id":{"description":"Crawl id from writ_crawl_site.","type":"integer"},"limit":{"description":"Max files to return (default 100, cap 200).","type":"integer"},"runs":{"description":"With `crawl`: how many recent completed runs to aggregate (default 1 — the current version of every document).","type":"integer"}},"type":"object"}
COLLECTCOLLECTSAaSITEsite(or a sectionof it)into adataset: a distributed Dragnet crawl that discovers pages and stores each one as aqueryable, change-trackedrow. For: 'crawl <site>', 'every page of the docs', 'all products in this category', 'adatasetof <site>', or anything queried, exported, monitored or re-run later. NOT FOR: reading one or a few pages now, which is writ_scrape(url / urls / top_n answer inonecall, no dataset); acting onrowaper page, which is).writ_browser_use. ModesScope:all three fetch pages the same way and differ in who reads each page. - CLASSICintent (default: extract_mode='markdown', executor='regular'): every page becomes clean markdown, no AI spent, fastest. Fits content, docs,aarticlessection,discussions (threads keep [top-level]/[reply · depthinN]plaintagswords),and any ask the two modes below do not cover. - SCHEMArank_cap=N (extract_mode='schema' + extract_schema): every page holdsthesame structuredtoprecordN(aitemsproduct,of a listingrow),returned as rows, not prose. Deterministic CSS extraction, no AI. - AI-ASSISTED (executor='ai' + extract_prompt): fields that need understanding and vary per page (sentiment, pros/cons, a classification, free-form values with no stable selector) across many pages. Each page waits on a model callseed_urls (~10s) and bills 5x the page rate; on a fewknown pages), oron fieldstheother modes capture, it adds cost and no accuracy. Scope: an unscoped crawl of a realwhole sitecollects hundreds of nav, tag and pagination pages and billscappedforbyeach.page_budget.ShapesModes:-A sectionmarkdown ('the docs'default,'the pricing and blog pages'): `intent` in plain language (the server derives include/exclude paths and depth from the site's realnoURLsAI)plus `relevance_threshold` ≈0.3,which drops off-goal pages. - Known pages as a dataset: `seed_urls`extract_mode='schema' (no discovery). The immediate answer isuniformwrit_scrape(urlsrecords). - Top N of a listing,asexecutor='ai'a+datasetextract_prompt (re-runper-pagelaterunderstanding,monitored): `rank_cap`=N. The immediate answer5xisthewrit_scrape(url,pagetop_nrate).-Whole site ('every page'): the defaults; `page_budget` caps the spend. Delivery: aA bounded crawl(rank_cap / seed_urls) waits andanswersreturnswith itspages in `data.rows` in this call;rows; an opensite crawlone returns acrawl idcrawl_id for writ_crawl_status,and its results land as a workflow dataset (writ_workflow_data, writ_search_data, writ_export_data). Comment and discussion threads are kept by content_spec {"preset":wait=true."full",NOT"include_comments"FOR:true} (rank_cap crawls set it already). Behindreading alogin: persona_id, whose saved session the crawl uses. `save_as` keeps the crawl for re-running; writ_saved_crawls lists those, each answering only the asks its `scope` covers. Response shape: by default every answer isfewWrit'spagesenvelopenow (definition + crawl status + a `data` table whose rows wrap `fields` in run bookkeeping, and whose records carry page metadata like `content_kind`/`depth`writ_scrape).`output` gives an API built on a crawl its consumer's shape: {shape:'record'} for one entity (a usage meter, a dashboard),Every{shapeoption:'records'}for a list, `fields` to pick/rename ('percent_used as pct', dotted paths), `exclude` to drop, `key` to wrap. Page metadata is stripped unless include_meta=true. Saved with save_as, it becomes the API's default shapewrit_help(overridable per call on writ_run_saved_crawl / writ_saved_crawl_data'writ_crawl_site').The metadata is added after the extract_prompt model answers, so `output` removes it and the prompt cannot.
{"properties":{"allow_subdomains":{"type":"boolean"},"content_spec":{"description":"Which elements of each page to keep: {preset: 'full'|'main', include_comments: bool, exclude_selectors: [css], include_selectors: [css], keep: {images: bool}}. 'main' = article body only; 'full' = the whole page including comment and discussion threads. Comments, replies and discussion survive only with 'full' plus include_comments; otherwise they are stripped out.","type":"object"},"delay_ms":{"description":"Politeness delay between fetches per host (default 250).","type":"integer"},"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"exclude_paths":{"items":{"type":"string"},"type":"array"},"executor":{"description":"regular (default) = deterministic crawl, noAI. ai = a fleet ofAIagents reads every page against `extract_prompt` and returns structured records, for data with no clean CSS selector.)Bills|atai (5x the pagerate.rate).","type":"string"},"extract_mode":{"description":"markdown (default) | schema(uniform records via extract_schema)|html (each page's raw HTML, for selectors or embedded JSON)html.","type":"string"},"extract_prompt":{"description":"Required withWith
Status of a crawl byid: page counts, status, the dataset workflow id. Withcrawl_id; wait=trueone callholds up to 75 s and returnswhenthecrawl converges, with thecollected rowsinline (`data`, shaped by `output`);when itresolves a crawl tool's 504 / crawl-id handle. A crawl still running at the ceiling answers with its current status, and the same call can be repeated.converges.
{"properties":{"crawl_id":{"description":"Crawl id from writ_crawl_site.","type":"integer"},"limit":{"description":"Rows to inline when it converged (default 50).","type":"integer"},"output":{"description":"Response shape, for an answer a program or an API consumes rather than a reader. {shape: 'envelope' (default: Writ's full answer, projected) | 'table' ({columns, rows, total}) | 'records' (bare list of records) | 'record' (the newest record alone: one entity, a usage meter, a dashboard), fields: ['used', 'percent_used as pct', 'items.0.price as first_price'] (ordered pick, renames, dotted paths; a missing path is null, so keys are stable), exclude: ['depth'], include_meta: false (page metadata content_kind/depth/thumbnails is stripped unless true), key: 'usage' (wrap)}. On writ_crawl_site with save_as it is saved as the API's default shape.","properties":{"exclude":{"items":{"type":"string"},"type":"array"},"fields":{"items":{"type":"string"},"type":"array"},"include_meta":{"type":"boolean"},"key":{"type":"string"},"shape":{"enum":["envelope","table","records","record"],"type":"string"}},"type":"object"},"preview_chars":{"description":"Cut inline text cells to this many chars (default 12000; 0 = full).","type":"integer"},"timeout_seconds":{"description":"Ceiling for wait=true (≤75).","type":"integer"},"wait":{"description":"Hold untilthe crawl is terminaldone and inlineits rows (defaultthefalse).rows.","type":"boolean"}},"required": ⟨3 unchanged words⟩
Creates an automation: on an event,it runs a workflow, sends a notification, and/or wakes an AI agent. It chains workflows(A completes → B runs), alerts on completion, or has an agent act on the event (`ai_prompt`). workflow_* events takeasourceworkflowin `on_workflow`; at least one of `run_workflow` / `notify` / `ai_prompt` is required. `notify` is a template over the event, so it carries the data, not just that it ran: after a workflow, {{result.extracted_data.0.title}} / {{result.extracted_data.<var>.0.url}} (the run's own rows); after aor crawl,{{rows.0.<field>}} … {{row_count}} (the records it collectedcompleted,schema fields included) plus {{seed_host}} {{pages_done}}; aftera monitor change,{{extracted.price}}. A missing path renders empty, soadigest of N rows is N numbered lines. Digest pattern: writ_set_schedule on the workflowwebhook,then this with when=workflow_completed;acrawl has noscheduleof its own: when='scheduled' + a `crawl` block, then this with when=crawl_completed. On a clock: when='scheduled' + `schedule` fires the actions at that time (a notify after run_workflow waits for that run, so {{result.extracted_data...}} is filled).`run_functions` + `inputs` call chosen functionsitofruns amulti-functionworkflow(what they need runs too). Anything else (conditions,scrape, extract, branches) is a raw `blocks` tree. On a webhook: when='webhook_received' mintssends asigned inbound URL; the answer's `webhook` holds the url, its signing_secret (shownnotificationonlytemplatedthen),over thetwo headers every callevent'ssignsdata,curl and Python examplesand/orandwakes anexample_body. Each top-level JSON field of a call becomes the run input of the sameAIname;agent.`inputs`Event,`notify` and `ai_prompt` templates read any field as {{payload.<path>}}. A call is acknowledged at once; with ?wait=true it is held until the workflow runs the automation starts finishtemplate andanswers their data. `webhook_trigger_id` reuses an existingwebhookURLoptions: writ_help(writ_list_webhooks'writ_create_automation').
{"properties":{"ai_entry_url":{"description":"Page the woken agent starts on. Defaults to the event's page (the monitored URL on change_detected); required in practice for workflow_*/webhook events.","type":"string"},"ai_prompt":{"description":"Wake an AI agent with thistask when the event fires. The agent gets the event context (page URL, diff, extracted values) and works the task in a cloud browser. Supports {{placeholders}}.","type":"string"},"ai_session_id":{"description":"With when='ai_session_completed' / 'ai_session_started': only this AI session.task.","type":"integer"},"blocks":{"description":"Raw flow tree instead of the action arguments (max 50): each {id, type: event|condition|action, blockType, config, parentId}. The first block is the root event (its blockType is the event: one of `when`; scheduled config {mode, interval_ms | time, days, tz}); every other block names an earlier block as parentId. Actions: workflow {workflow_id, function_name | function_names, input_mapping}; notification {template, title, channels, recipients}; ai_session {goal, entry_url}; scrape {urls:[<=5, templates ok], format: markdown|html|both, on_error} -> {{scraped.content}} {{scraped.pages}}; extract {source:'{{scraped.content}}', fields:[{key, from: html_css|json|regex|embedded_json|body, selector, attribute, all, path, pattern, group, number, required}]} -> {{extracted.<key>}}; crawl {seed_url, intent, include_paths, exclude_paths, page_budget, max_depth, extract_mode: markdown|schema, extract_schema, render_mode: auto|http|browser, persona_id} starts a fresh crawl (its rows arrive with crawl_completed, not in this chain); condition {field, operator, value}. Awaiting a run: an event block workflow_completed {linked_to_block:<workflow block id>}. Everystringsetting takes {{placeholders}}.","items":{"type":"object"},"type":"array"},"channels":{"description":"Notificationchannels for `notify`, e.g. [\"pushover\",\"email\"] (required for delivery).channels.","items":{"type":"string"},"type":"array"},"
CreatesDrafts or revisesanaadvancedmulti-request browserless HTTP extractionfrom plain language and real browser-network evidence(a browser experiment or capture_network). For: what one simple request or a named auth/function graph cannot express (requestloops, GraphQLdescriptor discovery,recursive JSON, cross-page dedupe, sorting,cursor pagination);ordinary login/bootstrap/data chains are writ_browser_compose define_function with is_auth/order/typed response_extractions. Generates a universal api_call.config.flow; site behavior stays inside the workflow. apply=false returnsfromthecaptureddraftrequests andits validation; apply=true writesavalid draft. It is ready to expose once writ_diagnose_http_workflow shows a run with engine=http and records. Requestsplain-languagearegoal.flowEverystepsoption: writ_help(no fetch in evaluate_js'writ_create_http_extraction'); Writ-only controls never reach the site..
{"properties":{"apply":{"description":"false (default)returns a reviewable draft;drafts; truewrites a valid draft to the workflow.writes.","type":"boolean"},"desired_inputs":{"description":"Caller parameters such as query, min_price, max_price, limit and cursor.","items":{"type":"string"},"type":"array"},"goal":{"description":"Exact inputs, output fields, filters, ordering and pagination behavior wanted.","type":"string"},"request_samples":{"description":"Relevant calls returned by writ_browser_network/capture_network, including representative response bodies when available.","items":{"type":"object"},"type":"array"},"requirements":{"description":"Extra mapping, dedupe, filtering or cursor requirements.","type":"string"},"response_sample":{"description":"Optional representative JSON/HTML response when it is not in request_samples.","type":"string"},"step_index":{"description":"api_call step to replace; defaults to the first, or appends one.","type":"integer"},"workflow":{"description":"Existing workflow name (or workflow_id).","type":"string"},"workflow_id":{"type ⟨5 unchanged words⟩
Creates a monitor that watchesWATCHES aURLpage forchanges/updateschanges: Writ checks it on a schedule and firesachange_detectedeventwhen the page, a CSS selector's text, a value read by `extract`, or a visual zoneof the pagechanges. Returns themonitor idmonitor_id that writ_wire_monitortakes. Selector proof: with the `session_id` of a writ_browser_use session openconnectsontotheanpagealert,the selector is checked on the live page before saving. One that matches several elements (Amazon '.a-price' matchesadozen; the check would join them into one blob) is pinned to the one shown; one that matches nothingworkflow oronlyanimage becomes a visual zone watch. With no selector at all, mode='visual' + zone_text=<the value exactly as the page prints it, e.g. '51,77 EUR'> watches that area'sAIpixelstask (digits areacompared,pricesowatch51.77takesfindsa'51,77thresholdEUR'there).A zone fires on any visual change, so it suits a change alert, not a threshold. `selector_check`Withoutin`interval` the answersays what was done. Interval and access: no `interval` →is needs_input:(this plan's options+and a tell_userwrittenfor theuser;user, nothingcreated); one the plan refuses is refused with the allowed ones.created. The page is read first:behinda sign-in→answersneeds_persona;needs_persona, a bot check→ an offer ofoffers use_residential.
{"properties":{"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"enabled":{"description":"Start the monitor enabled (default true).","type":"boolean"},"extract":{"description":"Browserlessalternative to `selector`: a response-extraction spec the check applies to a plain HTTPreadresponse:instead{\"from\":\"json\",\"path\":\"data.price\"}of(aJSON/XHR endpointselector,with request_url),e.g. {\"from\":\"html_css\",\"selector\":\".a-offscreen\"} (the page markup), {\"from\":\"regex\",\"pattern\":\"...\.price\"}(a value in a script/JSON blob).For a value readable without JavaScript (most prices and stock lines), no check opens a browser, so each is cheaper and harder to wall than a `selector`/requires_browser check. Structured data (an endpoint, JSON-LD, a JSON blob) survives a redesign best. Same grammar as api_call response_extractions.","type":["object","string"]},"interval":{"description":"How oftento check: an option id fromtheneeds_inputanswer
The user's linked Writ desktops(there can be several):which are connected right nowlist,and how many local workflows and personas each offers. action='list'use (default) shows them; action='use' device=<agent_id or name> scopesscope this connection to one: runs(writ_run_workflow),browser sessions (writ_browser_use), desktop workflows (writ_list_workflowsbrowsers,runs_on='desktop') and desktoppersonas(writ_personas,andsource='device')monitors then target it, and its run history (writ_workflow_runs),data (writ_workflow_dataclear,writ_search_data) andmonitors(writ_create_monitor,writ_wire_monitor; action='monitors' lists them) are read from and created on it. action='clear' removes the scope. A desktop's personas sign in from its own vault: the credentials never leave it. For: work 'on my laptop' or 'on my work computer', or in the user's own browser with their logins.datasets.
⟨4 unchanged words⟩ (default) | use | clear | monitors(that desktop's monitors)|datasets (what its exposed workflows collected).datasets.","enum":["list","use"," ⟨6 unchanged words⟩ :{"description":"use: the desktop's agent_id(orits exact name) from list.name.","type":"string"}},"required":[],"type":"object"}
Diagnoses HTTP-lane readinessExplainsforwhy a savedworkflow. Reports browser-only dependencies, invalid flow actions/expressions, unsafe internal query parameters, eligibility versus actual proof, and the next repair action. task_id (a representativeworkflow's run)confirms whether it really used engine=http, did not fall back, andreturnedrecords (an answer suchnothingasor{ok:true,count:0,listingsfailed:[]}is not proof). With task_id it also returns the run'sHTTP-lanestepsreadiness and,for a flow, what every request it made was answeredwith(status, sizestask_id,a response sample):thefirst evidence when a run returned nothing or failed. A 200 with an empty feed means the site stopped serving this session (signed out, blocked, or a null written over a working request variable),run'snotrequests'noandmatches'.responses.
{"properties":{"task_id":{"description":"Optional run to verify actual HTTP execution and output.","type":"integer"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"} ⟨2 unchanged words⟩
Status of abuild started by writ_website_to_api. Terminal states: succeeded, failed, cancelled. Resting state: needs_guidance — Writ's mechanical rungs are done andwrit_website_to_apithebuild.buildwait=true isthe caller's to finish; itonecarriesheld`map`call (endpoints seen,upspecs,tocandidate75functionss),and writ_website_to_api mode=guided build_id=<this id> continues it. A rung the ladder replaced reports superseded=true with fallback_build_id; with wait=true this toolthat followsthat pointertheandbuildanswerstowithitsthenewest rungnow running (`followed_from` listsandthereturnsidswhen itwalked; the returned build_idisthe current one from then on). `escalations` lists every earlier rung with the realdonereasonoritparked.handedAnswers:oversucceeded (robots.txt refused the crawl, no pages fetchedworkflow_id,nothing matched the goalfunctions,no structured list...run_example),i.e. why a browser was needed. On success it returns the workflow_id, the surfaces mappedfailed,and `verified` — false for a fast/browser mapcancelled,whose functions are candidates until a real run proves them. Any function runs withorwrit_run_workflowneeds_guidance (function_name);thegenerated API docsbuild(OpenAPIis3,theMarkdowncaller'sortoafinishPostmanwithcollection,writ_website_to_apiallmode=guidedpointingbuild_id=<id>;atitsthemaprealcomesWritwithendpointit)are at GET /api/v1/workflows/{workflow_id}/api-docs..
{"properties":{"build_id":{"description":"The build id from writ_website_to_api.","type":"integer"},"timeout_seconds":{"description":"Ceiling for wait=true (≤75up to 75).","type":"integer"},"wait":{"description":"One held call(≤75s) that follows escalations to the newest rung and returns when that build isuntilterminaldone orparked as needs_guidance. Still building at the ceiling, it answers with the build_id to repeat it with.parked.","type":"boolean"}},"required": ⟨3 unchanged words⟩
Exports a workflow's fullextracted-datadata table as CSV orJSON (search/filter applied, unpaginated).JSON.
{"properties":{"format":{"description":"csv (default)or|json","type":"string"},"q":{"type":"string"},"view":{"type":"stringjson."},"workflow":{"type":"string"},"workflow_id":{"type ⟨3 unchanged words⟩
Publishes a saved workflowthrough Writ's managed REST gateway (the same resourceasthe app'sa REST endpointswitch). The returned(a POST URLwaits for thethatworkflowwaits and returns its JSONresult.). Repeated calls reusethe existing endpoint.it.
{"properties":{"label":{"description":"Optional name for the endpoint.","type":"string"},"timeout_seconds":{"description":"Managed run timeout. Defaults to 120, or 300 for AI navigation workflows.","maximum":300,"minimum":5,"type":"integer"},"wait_timeout":{"description":"Deprecated alias of timeout_seconds.","maximum":300,"minimum":5,"type":"integer"},"workflow":{"type":"string"},"workflow_id":{"type" ⟨3 unchanged words⟩
The account's inbound webhook URLs:for each, its webhook_trigger_id, the callable url, whether it is signed,and the automationsit fires and how often it was called. Signing secrets are stored encrypted and never shown. An automation reuses one with writ_create_automation webhook_trigger_id=<id>; when='webhook_received' without it mints a newtheyURL.fire.
{"properties":{"search":{"description":"OptionalName/descriptionname/descriptionfilter,filter.e.g. a site.","type":"string"}},"type":"object"}
Pays on awebsitesite with one of the user'scards, without thecards; cardnumber ever reaching this conversation: the toolnumbers neversees or asks forreachcardthisnumbers.conversation.action='request'request (site, max_amount, purpose) returns a`tell_user`tell_user and an`open_url`: a Writ windowopen_url where the userpicks or adds a card (or makes a virtual card) and approves it. A purchase needs that approval. action='wait' grant_id=<id> is one held call (up to 60s) that answers when they approve, with the card's brand and last four digits only. Ordering:approves;action='checkout'wait (session on the final checkout page, commit_selector = the place-order button, total_selector = the order total,grant_id,ormax_amount when the store uses its own saved card) pauses the session and the user confirms (emailed link, the Writ app, or an auto-confirm rule they turned onconfirmation_id);then Writ types the card, checks the totalcheckoutandpausesclicksat the order buttonitself: a real purchase. An order-button click sent byfor theassistant isuser'srefused.confirmation,action='wait'thenconfirmation_id=<id>Writreturnsplaces theoutcome. action='fill' types anorder;approvedfill;virtuallist (cardearly (multi-pagehandlescheckoutsonly).Card use is limited to the site and amount granted; an unused grant expires after 15 minutes. action='list' shows the user's payment methods as handlesDetails: writ_help(kind, brand, last4, id'writ_payment'), never numbers; a handle goes into writ_wire_monitor buy.payment {kind, ref}.
{"properties":{"action":{"description":"request= the user approves a card for one purchase;| wait= hold until they answer (grant_id) or until a checkout is settled (confirmation_id);| checkout= pause at the order button for the user's confirmation, then Writ places the order;| fill= type an approved virtual card early; list = the user's payment methods as|handles.list.","enum":["request","wait","checkout","fill","list"],"type":"string"},"automation_id":{"description":"request: the automation the card is for, when the purchase is a saved automation.","type":"integer"},"commit_selector":{"description":"checkout(required):CSS selector ofthebutton that places the order. Writ clicks it after the userplace-orderconfirms.button.","type":"string"},"confirmation_id":{"description":"wait: the confirmation_id action='checkout' returned.","type":"string"},"currency":{"description":"request / checkout: the store's currency, three-letter ISO code ('eur', 'usd', 'gbp'); max_amount is in it. Omitted: the user's card currency on request, the currency the page total shows on checkout. A card pays only in its own currency.","type":"string"},"fields":{"additionalProperties":{"oneOf":[{"type":"string"},{"properties":{"frame_url":{"type":"string"},"selector":{"type":"string"}},"required":["selector"],"type":"object"}]},"description":"request, for live browsing: card field -> CSS selector on the checkout page (writ_browser_context reads them), or {selector, frame_url} for a field inside a payment provider's frame (frame_url = part of the frame's URL, e.g. 'js.stripe.com'). Keys: number, exp (MM/YY), exp_month, exp_year, exp_year2, cvc, name, zip. Writ types into exactly these.","type":"object"},"
The user's own accounts onwebsites. When a task needs the user signed inwebsites (email, social, shop, bank or work portalpersonas), a persona is: how Writ signs in as them. No password ⟨14 unchanged words⟩ credentials are typed only into a Writ window.A persona is a saved sign-in identity: a site's username plus credentials sealed server-side (never readable here), optional 2FA whose codes are minted server-side, and a warm signed-in session. Its persona_id is accepted by writ_browser_use, writ_crawl_site, writ_scrape and writ_run_workflow.action='list'(filter by domain)domain=<site> shows the personas usableon a site; action='get' inspects one (include_runs adds its recent runs); action='sign_in' runs its login workflow now (force=true re-logs-in even when the session looks usable); action='record_login' has a server-side AI sign in as it once and record the flow as its login workflow, so it can sign itself back in. This tool cannot create a persona. listthere; witha domain and nononematchit answers`persona_needed`persona_needed: a`tell_user` (a message writtentell_user for the user), a`create_url`create_url (aminted link to asmall Writ window with only thepersonasign-in form, pre-filled forthatthe site) andits `link_id`; action='request' domain=<site> mints one on purpose (another account forasite).link_id. action='wait'link_id=<link_id>link_id=<id> is one held call that answersthe momentwhen the user savesthe personait, with its persona_id. Alsolisted: the personas of the user'sget,linkedrequestWrit(adesktoplink(source='device',foridanother`device:<agent>:<id>`account),namesign_inand(refreshsitetheonlysession).Thatandidrecord_login.asA persona_idon writ_run_workflow, writ_browser_use /workswrit_record_website,on writ_scrapeor, writ_crawl_sitesends the work to that desktop,which signs in from its own vaultwrit_browser_use andonly on the persona's own site: the credentials never leave it. Scoped towrit_run_workflow; a desktop(writ_devices / `device`), list and request ask ONpersonait('device:its Writ app pops the form,...')andsigns`wait`in onthe `device:…`thatlink_iddesktop,returnsfromtheits`device:…`ownpersona_id.vault.
{"properties":{"action":{"description":"What to dolist (defaultlist).request = mint the persona link|forgeta|siterequest(domain);| wait= hold until the user|savessign_init|(link_id).record_login.","enum":["list","get"," ⟨3 unchanged words⟩ ,"record_login"],"type":"string"},"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"domain":{"description":"list:onlypersonas usable on thishost (suffix match), e.g. 'github.com'. With no match the answer is `persona_needed`, a request worded for the user.
⟨6 unchanged words⟩ as its own run_<name> tool on this server.Workflows are not exposed as individual tools by default; every one stays callable through writ_run_workflow, and the pinned list is capped. For: the few workflows the user runs often or wants to call as a tool from here.
{"properties":{"pinned":{"description":"true (default) pins; false unpins.","type":"boolean"},"workflow":{"description":"Workflow name (or workflow_id).","type":"string"},"workflow_id":{"type":"integer"} ⟨2 unchanged words⟩
Records a repeatablewebsitetaskas a workflow that replaysondemand. For recording, capturing, teaching, automating or repeating actions ona sitewhen the point is the task, not an API surface. Writ opensas areal cloud browser, returns an observation and runs no model there; theworkflowcaller(goalis+thestartbrainurl):writ_browser_act drivesthebrowser, writ_browser_compose authors what the recorder cannot see (inputssameasessioncalleraspasseswrit_browser_use,explicitlabelledsteps,withnameditsfunctions)goal,and writ_browser_save saves thedrivenfinishedwithtask.writ_browser_actTheand savedworkflow replays at zero AI cost (writ_run_workflow, or its own run_<name> tool once pinnedwithwrit_pin_workflow_tool) and can bewrit_browser_save.scheduled.A goal that asks for an API("turn <site> into an API", "an endpoint for") is routed automaticallygoes to writ_website_to_api'sintelligent ladder (the fast path, then Writ's AI browser rung), so the recording is a real build. Thatbuildfirst proposes the user's own matching workflows (existing_workflows) and ready-made marketplace APIs (marketplace_candidates); skip_existing / skip_marketplace bypass those.instead.
{"properties":{"auth_mode":{"description":"Browser recordings only: reuse verifies unknown saved auth before adoption; fresh_login records a new login without restoring old cookies, headers or storage. Device identity stays the same.","enum":["reuse","fresh_login"],"type":"string"},"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"fresh_exit":{"description":"Residential only: skip the persona's usual exit for this site and draw a new address — after the site refused it (an IP rate limit, a sign-in rejected with correct credentials). Ignored on server-IP egress.","type":"boolean"},"goal":{"description":"What should be recorded on the website, in plain language","type":"string"},"human_layer":{"description":"Browser recordings only: native keyboard input, distance-adaptive mouse paths and pre-click dwell. Fresh login enables it by default; false explicitly disables it. Native actionability checks remain active.","type":"boolean"},"persona_id":{"description":"Saved identity to sign in with (list them with writ_personas). Required for sites behind a login with 2FA — the one-time code is then minted server-side and never shown to the caller. A desktop persona ('device:…') records on its own desktop, which fills its values itself.","type":["integer","string"]},"residential_country":{"description":"Two-letter ISO country the residential exit should be in (e.g. 'us', 'fr') — for a site that serves a different page per country, or throttles foreign traffic. Omit for an automatic exit. Ignored unless the session egresses residential.","type":"string"},"skip_existing":{"description":"API builds first propose the user's OWN matching workflows (replaying is instant and free); set true after the user declined those.","type":"boolean"},"skip_marketplace":{"description":"API builds then propose compatible ready-made marketplace APIs; set true to skip that and record fresh.","type":"boolean"},"url":{"description":"Website URL to start on (required).","type":"string"},"use_residential":{"description":"Open on the platform residential
Runs a savedcrawl with its stored settings. With `max_age`, the data it already collected comes back whencrawl;thatmax_agerunreturnsisits recentenough (the cheap path); otherwise it re-crawls. The response'sdata`_cache.hit`atandno`_cache.age_seconds`costsayinsteadwhichofhappened.re-crawling.
{"properties":{"crawl":{"description":"Saved crawl slug, name, or id (from writ_saved_crawls).","type":"string"},"limit":{"description":"Rows of collected data to include (default 50).","type":"integer"},"max_age":{"description":"Reusethe last completed crawl if it finished within this many seconds. 0 (default) always re-crawls.","minimum":0,"type":"integer"},"output":{"description":"Response shape, for an answeraprogram or an API consumesrunratheryounger thana reader. {shape: 'envelope' (default: Writ's full answer, projected) | 'table' ({columns, rows, total}) | 'records' (bare list of records) | 'record' (the newest record alone: one entity, a usage meter, a dashboard), fields: ['used', 'percent_used as pct', 'items.0.price as first_price'] (ordered pick, renames, dotted paths; a missing path is null, so keys are stable), exclude: ['depth'], include_meta: false (page metadata content_kind/depth/thumbnails is stripped unless true), key: 'usage' (wrap)}. On writ_crawl_site with save_as it is saved as the API's default shape.","properties":{"exclude":{"items":{"type":"string"},"type":"array"},"fields":{"items":{"type":"string"},"type":"array"},"include_meta":{"type":"boolean"},"key":{"type":"string"},"shape":{"enum":["envelope","table","records","record"],"type":"string"}},"type":"object"},"preview_chars":{"description":"Cut each inline page's text cells tothismany characters(default 12000; 0 = full). Full page: writ_workflow_data(workflow_id=<data_workflow_id>, refs=[...]seconds).","type":"integer"},"timeout_seconds":{"descriptionminimum":"Max seconds to wait when wait=true."0
Runs a saved workflow byidworkflow_id or name and,returns its extracted data (it waits by default,).waitsInputsforgoitastotop-levelfinish,fieldsreturningor under `inputs`; function_name runs one function of a multi-function API (with theextractedsign-indata.itWorkflowneeds).inputsmax_agegoreuses a recent result at no cost. A write function previews with mutation_mode='dry_run'. Runs sign in astop-levelthefieldsworkflow's persona orunderpersona_id;`inputs`;nofilepasswordinputsisgoeverunderpassed.`files`.Venue, network, files and output shape: writ_help('writ_run_workflow').
{"properties":{"device":{"description":"Run on this linked Writ desktop (an agent_id from writ_devices) — overrides the desktop this connection chose with writ_devices action='use'.","type":"string"},"execution_target":{"description":"Where this run executes:cloud'cloud'|(managedautofleet),|'auto'local (preferthe user'sOWN linked Writ desktop app when online, else cloud), or 'local' (require theirown desktop app— keeps the run on their machine + IP).Omit to keep the workflow's own configured target. A 'local' run fails while that desktop app is offline; 'cloud' runs the same workflow on Writ's fleet, off the user's machine and IP.","type":"string"},"files":{"additionalProperties":{"type":"string"},"description":"Optional file inputs for this run, as {slot: file_id}. Slot names come from the workflow's `file_slots` (writ_list_workflows); file_ids come from the account's file library. An upload step with a pinned file uses it when `files` is omitted; a value here swaps the file for this run only.","type":"object"},"function_name":{"description":"Onenamedfunction of a multi-functionworkflow (an API built with writ_website_to_api / writ_browser_compose define_function): only that function and the sign-in functions it depends on run. Omitted, the whole workflow runs. It is a control, never a workflow input.","type":"string"},"function_names":{"description":"Several functions in one run, in place of `function_name`: the union of their steps runs once, in recorded order (a prerequisite they share runs once). Not for a desktop (`local:`)
Reads the data a saved crawlalreadycollected on itsmost recentlast completed run,at any age. It never starts awithoutcrawl.crawling.
{"properties":{"crawl":{"description":"Saved crawl slug, name, or id.","type":"string"},"limit":{"description":"Rows to return (default 25).","type":"integer"},"output":{"description":"Response shape, for an answer a program or an API consumes rather than a reader. {shape: 'envelope' (default: Writ's full answer, projected) | 'table' ({columns, rows, total}) | 'records' (bare list of records) | 'record' (the newest record alone: one entity, a usage meter, a dashboard), fields: ['used', 'percent_used as pct', 'items.0.price as first_price'] (ordered pick, renames, dotted paths; a missing path is null, so keys are stable), exclude: ['depth'], include_meta: false (page metadata content_kind/depth/thumbnails is stripped unless true), key: 'usage' (wrap)}. On writ_crawl_site with save_as it is saved as the API's default shape.","properties":{"exclude":{"items":{"type":"string"},"type":"array"},"fields":{"items":{"type":"string"},"type":"array"},"include_meta":{"type":"boolean"},"key":{"type":"string"},"shape":{"enum":["envelope","table","records","record"],"type":"string"}},"type":"object"},"preview_chars":{"description":"Cut string cells (page markdown) to this many characters (default 2000; 0 = full cells). Full single pages: writ_workflow_data(refs=...) per the response hint.","type":"integer"}},"required":[" ⟨3 unchanged words⟩
Lists the crawlsthe usersaved for re-running(callable by API,eachwitha `scope`: seed_url, rank_cap, include_paths, extract_mode, executor). On a saved crawl whose scope matches an ask, writ_run_saved_crawl(max_age=…) returns recent data instantly at no cost. A saved crawl of a different page, or one using executor=ai, does not answer a fresh question; writ_crawl_site(rank_cap=N)theirdoes.scope.
{"properties":{"limit":{"description":"Max saved crawls to return (default 50).","type":"integer"}},"type":"object"}
READPAGE CONTENTPAGES NOW: one page,a list of pages, or the topnoNbrowseritemssession:ofoneapagelisting(url),returnedupastoclean20markdownpages inthis callparallel (2-10surls). For: 'what does,<page>orsay',a'summarizelisting<url>',plus'theits top Nposts/products/resultsitemofpages<listing>(urland+what'stop_n).onReturnseach',clean'fetchmarkdownthesein32-10links's, includinga pagepages a plain fetch cannot read:blocked or empty (403, bot wall),rendered by JavaScript,or behind the user's sign-in(render_mode, use_residential, persona_id). NOT FOR: collecting a whole site or section into a dataset (writ_crawl_site); clicking, typing, signing in or any action onrender_mode='browser'; apage (writ_browser_use). Three shapes, one call each: - url →read thatpage. - urls=[...] (≤20) → all of them, fetched in parallel, `pages`onlyingot theorder given. - url=<listingpage'spage>shell+saystop_n=Nso(≤20)and→gives thelisting (`listing`retry)and the N,top-rankedbehinditemapagesbotitwalllinksorto403 (`pages`,aninautomaticrankresidentialorderretry); e.g. url='https://news.ycombinator.com/',top_n=3 returns the front page and the 3 top stories' discussion pages. include_paths=['item\\?id=']orgivesbehind theitem-link shape; without it theuser'sserversign-indetects(persona_id).it.Discussionpagesthreads keep theircomment threads; each comment is tagged [top-level] or [reply · depth N], so 'the top-level comments' iscomments.answerableformat='html'fromreturns thetext. Long pages are preview-cut at 12000 chars (`_truncated`); theraw`hint`HTMLsaysforhowselectors.toNOTfetchFOR: afull page. The caller reads the markdown; no AI is spent here.site-wideBehinddataseta(writ_crawl_site)login:orpersona_id.clickingBotandwall:typinguse_residential=true.(writ_browser_use).
{"properties":{"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"format":{"description":"markdown (default):the page's clean main content.| html:the(raw HTMLas fetched (same egress/persona/render)— for selectors, embedded JSON, anything the cleaned text drops; clipped to preview_chars (default 40000, 0 = whole page). both: html plus the markdown derived from it, one fetch. Single `url`|only.both.","enum":["markdown","html","both"],"type":"string"},"include_paths":{"description":"With top_n: regex(es) the item links match (e.g. 'item\\\\?id=', '/products/'). Optional: the server detects detail links when omitted.","items":{"type":"string"},"type":"array"},"persona_id":{"description":"Saved identityReadtosignedreadin as(writ_personas lists them), for pages behind a login. Forces the identity's own residential exit. Athis
Searches everything the account's workflows already collected for a term: answers data questions from past runs, without running anything.Scopes to one workflow when given, else fans out. Matches come back preview-sized; writ_workflow_data(refs=...) returns full records.
{"properties":{"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"limit":{"description":"Rows per workflow (default 10).","type":"integer"},"preview_chars":{"description":"Cut matched string cells to this many characters (default 300; 0 = full cells).","type":"integer"},"q":{"description":"Search term (required).","type":"string"},"workflow":{"type":"string"},"workflow_id": ⟨6 unchanged words⟩
Schedules a saved workflowto run automatically:`every_minutes` for an intervalevery_minutes, or`kind`='daily'/'weekly'kind='daily'/'weekly' with`time`time (HH:MM) and`days`. On a multi-function workflow (an API from writ_website_to_api) `functions` schedules one or several of itsdays. functionsinstead of the whole workflow, with saved `inputs` for them: each+runinputsexecutesschedulethosechosen functionsplus whatever they need (sign-in,of atoken, the search whose ids another reads) exactly once. The answer names `also_runs` and anymulti-function`missing_inputs`.API.
{"properties":{"days":{"description":"ISO weekdaysWeekdaysfor1=Mon..7=Sun (weekly: 1=Mon .. 7=Sun.).","items":{"type":"integer"},"type":"array"},"enabled":{"description":"Schedule on or off (default on).","type":"boolean"},"every_minutes":{"description":"Intervalschedule: minutes betweeninruns.minutes.","type":"integer"},"function":{"description":"One function name (alias of `functions`); \"\" or \"all\" = the whole workflow.","type":"string"},"functions":{"description":"Function names a scheduled run calls (from writ_list_workflows / writ_update_workflow). [] or [\"all\"] = the whole workflow. Omitted: current target kept.","items":{"type":"string"},"type":"array"},"inputs":{"description":"Scheduled-run inputs {input_name: value} (text, number, true/false), used over the workflow's saved values for scheduled runs only. {} clears them; omitted keeps them. No secrets: those are workflow secrets or a persona.","type":"object"},"kind":{"description":"interval | daily |weeklyweekly.","type":"string"},"time":{"description":"HH:MMlocal time for daily/weekly.(daily/weekly).","type":"string"},"tz":{"description":"IANAtimezone for daily/weekly.","type":"stringtimezone.
InspectInspects orcustomizechanges a savedcrawlcrawl's settings in place.With no changes, returns its complete definition. `settings` recursively merges any crawl option into the stored config (scope, paths, budgets, extraction, rendering, persona, residential egress/country, speed, output shape and other flags) without dropping unrelated settings.
{"properties":{"crawl":{"description":"Saved crawl slug, name, or id.","type":"string"},"default_max_age_seconds":{"minimum":0,"type":"integer"},"description":{"type":"string"},"name":{"type":"string"},"settings":{"description":"Sparsecrawl config patch merged recursively into the complete savedsettingssettings.patch.","type":"object"}},"required": ⟨3 unchanged words⟩
Inspectsandor edits a savedworkflow. No edits returns aworkflow:compactan outlinewith stable step ids and source hashes; verbose=true reads the full definition. section=contract lists every HTTP operator with its operands and what it does (operator selects one); section=flow + function_name lists nested node paths. function_name reads, one function's source,with optional step_id and path (JSON Pointer, e.g.targeted/config/flowfunction or/config/script); offset/max_chars window the source. Targeted edit: function_updates=[{function_name, step_id?, expected_hash?, patch?, json_edits?, script_edits?}]. An api_callstepbinds config.function_name. patch merges type/config/enabled; json_edits use test/add/replace/remove with path/value to change one nested HTTP action, JSON field or array item without rewriting the program. script_edits use path/old/new and optional expected_hash; old must match exactly once. Function identity is preserved. validate_only=true previewseditsand validates HTTP grammar without running JavaScript or making requests. expected_updated_at from, aread preventssettingsconcurrentpatch.overwriteEvery(409option:stale readwrit_help('writ_update_workflow').function_updates is the targeted repair; step_updates merge indexed steps; replace_steps replaces all steps. patch edits settings and metadata; credentials stay in persona/vault. Saving runs nothing; a run on representative inputs, diagnosed by its task_id, proves the edit. regenerate_skill=true rebuilds the agent skill; patch.skill_md edits it.
{"properties":{"expected_updated_at":{"description":"Workflow timestamp returned by inspection; checked under the row lock.","type":"string"},"function_name":{"description":"Source of this function only.","type":"string"},"function_updates":{"items":{"additionalProperties":false,"properties":{"expected_hash":{"description":"Expected whole-step hash from inspection.","type":"string"},"function_name":{"type":"string"},"json_edits":{"items":{"additionalProperties":false,"properties":{"op":{"enum":["test","add","replace","remove"],"type":"string"},"path":{"type":"string"},"value":{}},"required":["op","path"],"type":"object"},"type":"array"},"patch":{"description":"Sparse type/config/enabled patch; object keys merge, arrays replace.","type":"object"},"script_edits":{"items":{"additionalProperties":false,"properties":{"expected_hash":{"type":"string"},"new":{"type":"string"},"old":{"type":"string"},"path":{"type":"string"}},"required":["path","old","new"],"type":"object"},"type":"array"},"step_id":{"type":"string"}},"required":["function_name"],"type":"object"},"maxItems":50,"minItems":1,"type":"array"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"max_chars":{"maximum":20000,"minimum":1000,"type":"integer"},"offset":{"minimum":0,"type":"integer"},"operator":{"description":"section=contract: one operator, with its operands, semantics and example.","type":"string"},"patch":{"description":"Sparse settings patch. Supports every WorkflowUpdate field except credentials and captured recorded_session material, which vault/persona/session management own. human_behavior: 'on' types text using keyboard events every browser run; 'auto' does so on a bot-block retry; 'off' disables the default.","type":"object"},"path":{"description":"JSON Pointer into the selected step, e.g. /config/flow/steps/2 or /config/script.","type":"string"},"regenerate_skill":{"description":"Rebuild the workflow's agent skill (SKILL.md) from its current functions, inputs and sign-in, replacing any edited version; the answer carries the new skill_md. Runs after any patch in the same call.","type":"boolean"},"replace_steps":{"description":"Complete recorded-step replacement; step_updates is the targeted edit.","items":{"type":"object"},"type":"array"},"section":{"description":"source reads a source window; flow lists nested node paths; contract lists operators and their semantics without requiring a workflow.","enum":["source","flow","contract"],"type":"string"},"step_id":{"description":"Stable step id that picks one step of a multi-step function.","type":"string"},"step_updates":{"items":{"properties":{"index":{"description":"Zero-based step index.","type":"integer"},"patch":{"description":"Sparse step patch: id, type, enabled, and/or nested config. Config is merged recursively.","type":"object"}},"required":["index","patch"],"type":"object"},"type":"array"},"validate_only":{"description":"Preview/validate without saving or executing.","type":"boolean"},"verbose":{"description":"Edits answer with the full workflow definition instead of the compact step outline (default false).","type":"boolean"},"workflow":{"description":"Workflow name (or workflow_id).","type":"string"},"
TurnsTURNSaAwebsiteWEBSITEintoINTOaAcallableCALLABLE API:the one(namedtoolfunctionsforwiththisinputs,everyreturninglane.JSON)Forfor a service with noofficial orpracticalAPI whose data or actions are wanted programmatically: "turn <site> into an API", "map the API of <site>", "expose every feature", "give me an endpoint for <site>". A buildofficialisAPI. 3CALLScalls: (1) this tool with url+ goal (+ save_as); (2) writ_discovery_status(build_id, wait=true), one=heldthecallpage thatfollows every rung; (3) onalready`succeeded`,shows theanswer's `run_example`rows (writ_run_workflow: workflow_id + function_name + inputs) runs it, andaworking function answers two different inputs differently. START ON THE PAGE THAT ALREADY SHOWS THE ROWS: the url is thesearch-results/ category /or listing URL(e.g. https://www.google.com/maps/search/bakeries+Montreal/), not theapp'shomepage; a build seeded at an empty shell spent 6 minutes over three rungspage) andproduced no function. Agoalnaming= the inputs andthefields("page number in; quotes with text/author/tags and has_next out") shapes the functions. The build has its own browser, and an identical request during it joins it. An answer of existing_workflows / marketplace_candidates is a proposal, not a build: the match runs as is, and skip_existing / skip_marketplace start a fresh build. Status needs_guidance means the build is the caller's: mode=guided build_id=<id> continues it in a browser the caller drives as its brain. writ_diagnose_http_workflow(workflow_id, task_id) explains a run that returns nothing. NOT FOR: reading a page's content (writ_scrape), collecting a site as a dataset (writ_crawl_site), or a task that is not an API surfacewanted; (writ_record_website2). Login:an app behind a sign-in builds as a saved identitywrit_discovery_status(persona_id from writ_personasbuild_id,which also carries 2FAwait=true);credentials never pass through this tool. Without one, `persona_needed` carries `tell_user`, written to ask the user for that sign-in. Default mode intelligent: Writ runs the whole ladder itself, and the caller only starts it and waits. The ladder: the user's own matching workflows(existing_workflows; skip_existing=true bypasses), ready-made marketplace APIs (marketplace_candidates; skip_marketplace=true3),then the fast path: one real cloud browser (the persona's session, residential exit, CAPTCHA and bot-wallonhandlingsuccess,like every Writ session) where one AI call plans the functions the goalitsneedsrun_example (GET reads, POST writes,writ_run_workflowin-pagewithextractionsfunction_name)and the steps that reach each, the browser runs them, and per function one more AI call picks.whatWritbacksbuilds it— the site's own captured request (compiled: tokens traced, inputs templated), the page's list, or the write's captured requestitself (probe_write: never sent) — each live-tested, reads proven on a second input. Only when it cannot prove them does Writ'san AIbrowser rung take over (turn by turn: ranks traffic, promotes HTTP requests, tests pagination); the newer rung's status carries `escalations`, the reason thefast pathhanded over. A saved fast-path API with gaps names themin`missing_functions`. mode=crawl / mode=browser run the whole-site crawl rungs instead (static / rendered; robots.txt respected unless respect_robots=false): broad maps of server-rendered sites, unverified (`verified:false`) untilarun proves them. mode=auto is the same ladder with the caller as the last rung: when the fast path cannot prove the functions, the build parks as status=needs_guidance with `map` (pages, capturedrealcallsbrowser, eachplannedfunctionand why it fell shortlive-tested) andwhat it already defined, instead of spending Writ's agent. mode=fast, mode=crawl and mode=browser start on their own rung andfirstparkproposes thesameuser'swaymatchingwhensavedtheyworkflowsfallandshort.marketplacemode=guidedAPIs (with build_id=<that id> to continue, or alone to start) opens a real browser boundskip_existingto/theskip_marketplace build,driven turn by turn with writ_browser_act (navigate, sign in, capture_network, evaluate_js; calls read with writ_browser_networkfresh), where writ_browser_compose define_function defines the API: api functions from captured calls via from_index, or proven scripts/extractions, each live-tested as it is defined; set_inputs for parameters; is_auth for.aA sign-infunction whose response_extractions feed the others.wallwrit_browser_saveanswerssettlespersona_needed.theNOTbuildFOR:the workflow is callable atreadingoncecontent (writ_run_workflow with function_namewrit_scrape),pinnable, schedulable,orexposableaasone-offRESTtask (writ_expose_workflow_apiwrit_browser_use), and its API docs are at GET /api/v1/workflows/{workflow_id}/api-docs. HTTP-first: the guided browser is the experiment bench, and what ships are direct API functions defined from a captured representative search/filter.requestModes andnext-page request. The default shape istheAuphan-style named function graph: ordered is_auth functions publish tokens/ids/origins through response_extractions, and data functions consume {{extracted:name}}; config.flow covers loops, recursive mapping, cross-page dedupe and composite returns. Typed extractionguidedsourcesbuild:json, embedded_json, html_css, regex, header, body. search/filter/limit/page/offset/cursor are declared inputs, and next_cursor/next_offset/has_more are returned. writ_diagnose_http_workflowwrit_help(task_id=...'writ_website_to_api')checks a saved run made with the intended persona; an API is ready to expose once engine=http returns non-empty data and pagination matches the browser baseline, or once a measured browser-only dependency is named..
{"properties":{"ai_supervise":{"description":"AI-supervised crawl rungs (mode=crawl/browser; default true): after the crawl mines forms, POSTs, query links, scripts and listings, one bounded Writ AI call authors the API from them — which functions serve the goal, their names, inputs and example values; the live verify call then measures each response shape. false = the purely mechanical surface map (no AI spend on the crawl rungs).","type":"boolean"},"anonymous":{"description":"Builds from what is visible without an account even though the site shows a sign-in page; for when the user said the public part is enough.","type":"boolean"},"build_id":{"description":"Continues a parked build (status needs_guidance from writ_discovery_status) on the guided rung: opens the browser bound to it, seeded with its map.","type":"integer"},"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"execution_target":{"description":"'cloud' (the fleet) or a linked desktop's agent_id: builds there, in its own browser and connection. Omitted = the desktop chosen with writ_devices, else the cloud.","type":"string"},"goal":{"description":"What the API should return or do, in plain language. Matched to the user's own workflows and marketplace listings first; narrows the crawl to what was asked (equivalent to `scope`) instead of mapping the whole app.","type":"string"},"level":{"description":"Write policy for the crawl rungs. 'light' (default) captures every endpoint and payload but never performs a real create/update/delete. 'deep' performs each write once to capture its real confirmation response, so it changes real data; opt-in, for an explicit request.","enum":["light","deep"],"type":"string"},"mode":{"description":"intelligent (default): the full ladder driven by Writ — the AI fast path (a live browser,a few AI steps, everyWritfunctionbuilds
WiresConnects awrit_create_monitormonitor's change_detected event to anaction. action='workflow' runs a saved workflow when the monitored page changes;action: action='notify'sends a notification to `channels`(channels+and`recipients`recipients the account hasconfigured; action='ai_task'configured),wakes'workflow'an(runsAIaagentsavedwithworkflow),aortask'ai_task'`prompt`:(antheAI agent opens themonitoredpagein a cloud browser, sees what changed (diff + extracted values)and worksthe prompt autonomously (`channels`/`recipients` also notify when it finishes`prompt`). A price watch takes `threshold` (+ threshold_op): the action then runs once, when a check reads a price at or belowit (`threshold_op` picks the side), not on every change.it. Buying atthea price,in order: 1) writ_create_monitor reads the price; 2) writ_record_website or writ_browser_use, then writ_browser_save, record the checkout up to the order page, with the product, quantity and shipping as inputs where they vary, an `extract` of the total, and the order button never pressed(its selector kept); 3) writ_run_workflow with mutation_mode='dry_run' rehearses it once, to end on the order page and return the total (safe to rehearse only a checkout recorded this way: an older one may hold the order click); 4) action='workflow' + that workflow +`buy`(payment {kind, ref} from writ_payment, total_selector, commit_selector = that button (Writ clicks it live), fallback_ai_session:true); 5) the fallback, ONLY when the checkout can't be recorded or its rehearsal fails (bot wall, a checkout that won't replay, a login the persona can't hold): action='ai_task' with a `prompt` naming what to buy and the same `buy`; an AI PURCHASE session browses to the order page and hands over to Writ's confirmed checkout. Either way it is saved as a REHEARSALwrit_help(dry run'writ_wire_monitor')until the user turns purchases on in the Writ app, and a person confirms each purchase unless an auto-confirm rule they set covers automation purchases. The answer's `buy.runs_as` names the path: recorded_checkout or ai_purchase_session..
{"properties":{"action":{"description":"What to donotifyon|aworkflowdetected|change.ai_task.","enum":["workflow","notify","ai_task"],"type":"string"},"ai_session_id":{"description":"action='ai_task': re-run this saved AI session instead of (or as well as) a prompt.","type":"integer"},"buy":{"description":"action='workflow':Run the workflowis a checkout andorrunstask as apurchase. action='ai_task' (with a `prompt` saying what to buy): an AIpurchasesession browses to the order page and Writ checks out. Always saved with dry_run on(a rehearsal that places no order), whatever is sent; the user arms real purchases in the Writ app. Cloud monitors only; an AI purchase session can't use device_card.","properties":{"commit_selector":{"description":"action='workflow' only: the place-order button on the final page (CSS, at most 500 characters). Never click it: Writ clicks it during a live buy. A live buy needs it (or a recorded order step, or fallback_ai_session).","type":"string"},"confirm_over":{"description":"Ask for approval only above this amount (default: the threshold).","type":"number"},"fallback_ai_session":{"description":"action='workflow' only: when the recorded checkout fails at fire time, or has no order step, wake one AI purchase session with the same limits (once per fire).","type":"boolean"},"max_amount":{"description":"AI purchase session: the most one order may cost (0.01-10000). The page and the agent may lower it, never raise it.","type":"number"},"payment":{"description":"Required. {kind, ref}: kind vault_card | virtual_card | device_card (ref = the card's id from writ_payment action='list', or an approved grant's payment.ref) or merchant_saved (the card
Readsthe accumulated extracted data ofa savedworkflowworkflow's collected data as a table(columns + rows).:`q`qfilters;filters,`run_id`run_idselectspicks onerun. Long text cells arrive preview-cut (`_truncated` lists the fields);run,`refs`refs returns full records.Crawl ids are a different namespace: a writ_crawl_site run's data lives behind writ_crawl_status / writ_saved_crawl_data, not here.
{"properties":{"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"limit":{"type":"integer"},"output":{"description":"Response shape, for an answer a program or an API consumes rather than a reader. {shape: 'envelope' (default: Writ's full answer, projected) | 'table' ({columns, rows, total}) | 'records' (bare list of records) | 'record' (the newest record alone: one entity, a usage meter, a dashboard), fields: ['used', 'percent_used as pct', 'items.0.price as first_price'] (ordered pick, renames, dotted paths; a missing path is null, so keys are stable), exclude: ['depth'], include_meta: false (page metadata content_kind/depth/thumbnails is stripped unless true), key: 'usage' (wrap)}. On writ_crawl_site with save_as it is saved as the API's default shape.","properties":{"exclude":{"items":{"type":"string"},"type":"array"},"fields":{"items":{"type":"string"},"type":"array"},"include_meta":{"type":"boolean"},"key":{"type":"string"},"shape":{"enum":["envelope","table","records","record"],"type":"string"}},"type":"object"},"preview_chars":{"description":"Cut string cells to this many characters (default 2000; 0 = full cells).","type":"integer"},"q":{"description":"Substring filter across fields.","type":"string"},"refs":{"description":"Hydration: full untruncatedFull records byref'<run_id>:<record_index>' (both fields are on every row). Max 100.<record_index>'.","items":{"type":"string"},"type":"array"},"run_id":{"type":"integer"},"view":{"description":"all | latest | run","type":"string"},"workflow":{"type":"string"},"workflow_id":{"type":"integer"} ⟨2 unchanged words⟩
Run history (status, timing, errors,and the `venue` each run executed on: cloud or the user's desktopvenue)forof one workflow oracrossall of them.
{"properties":{"device":{"description":"A linked Writ desktop's agent_id (writ_devices) to act on. Omitted: the desktop this connection chose with writ_devices action='use', if any.","type":"string"},"limit":{"type":"integer"},"status":{"description":"pending|running|success|failed|cancelled|skipped","type":"stringFilter by status."},"workflow":{"type":"string"},"workflow_id":{"type ⟨3 unchanged words⟩
Writ tool reference — The full reference of any Writ tool (every parameter and its rules) and the complete guide. topic = a tool name (e.g. 'writ_browser_compose', a 'run_<workflow>' tool), 'guide' for the manual (recurring digests, HTTP flow building, buying on a price, crawl and build modes), 'actions' / 'recording' / '2fa' for the browser rules, or empty for the list of topics.