Press / to search

All documentation
docs Teach the agent Record a workflow

Runs onWrit CloudDesktopSelf-hosted

workflows ▸ record or describe

Workflows.

A workflow is an ordered list of steps that runs in a real browser and returns structured data. Author it once — by recording or by describing — then run it on demand, on a schedule, from a webhook, or as a published endpoint and MCP tool.

Writ runs on your own accounts, with your own credentials and data, on sites you are authorized to use.

object ▸ the shape

The workflow object.

A workflow is plain JSON: a name, an ordered steps array, and the declared-input defaults its placeholders resolve against. Each step is a small object with a type and a config.

{
  "name": "Product extractor",
  "description": "Prices from the catalog",
  "workflow_type": "recorded",
  "steps": [
    { "type": "navigate", "config": { "url": "{{url}}" } },
    { "type": "extract",  "config": { "fields": {
        "title": ".product .title",
        "price": ".product .price"
    } } }
  ],
  "form_data": { "url": "https://example.com/catalog" },
  "timeout_ms": 120000,
  "headless": true
}

author ▸ record or describe

Record it, or describe it.

There are two first-class ways to produce that JSON — and a third for sites with a usable API underneath.

PathHow it works
RecordClick through the site in the recorder; every click, fill, and navigation becomes a replayable step with a stable selector.
Describe (Scribe)Tell Scribe the goal in plain words. An AI session drives a live browser toward it and records the steps that worked into a reusable workflow.
API discoveryWhile you browse, Writ watches the page's own network calls and can build api_call steps from the endpoints it finds — often faster than driving the UI.

Both paths end in the same place: an editable step list. A described workflow is not a black box — you can read it, trim it, and re-record any part of it. See AI sessions for the describe path in full.

steps ▸ the vocabulary

30+ step types.

Steps run in order, and each one can read what earlier steps produced. The vocabulary spans navigation, interaction, waiting, extraction, tabs, AI, authentication and flow control — every type is listed in the step reference with its fields, a real example and its behavior.

Navigation

navigatenavigated_to

Interaction

clickhoverpressfocusfilltypeselectcheckuncheckscrollscroll_into_viewupload

Waiting

waitwait_for_changewait_for_download

Extraction

extractevaluatecodegenscreenshotapi_call

Tabs

open_tabwait_for_tabswitch_tabtab_closed

AI

ai_fillai_fill_formai_continueai_navigate

Authentication

login_posttwofacaptcha

Flow

returnend_pointassert

Open the step reference →

io ▸ inputs and outputs

Inputs in, structured data out.

A run carries its inputs in the form_data body field. Inside step values, placeholders resolve at run time — so the recipe stays generic and nothing sensitive is stored in it:

PlaceholderResolves to
{{key}}The matching key from the run’s form_data, falling back to the workflow’s saved defaults.
{{vault:name}}A secret from your vault, injected at run time. Secrets are interpolation inside a step value — never a step of their own.
{{extracted:key}}A value a previous step extracted in this same run — for chaining api_call requests.
{{file:slot}}A stored file bound to the named slot (uploads, captured downloads).

On the way out, extract steps fill extracted_data and the run settles with result_data — the structured payload your caller reads.

run ▸ the api

Running a workflow.

One endpoint starts a run. By default the call waits for the verdict; turn wait off to get a task handle back immediately.

POST /api/v1/workflows/{workflow_id}/runs?wait=true&timeout=120
Authorization: Bearer wt_xxxxxxxxxxxx
{ "form_data": { "url": "https://example.com/catalog" } }

# wait=true (default) — the call blocks until the run settles:
{
  "status": "success",
  "success": true,
  "result_data": { "title": "…", "price": "…" },
  "extracted_data": { "title": "…", "price": "…" },
  "error": null,
  "duration_ms": 8412
}

# wait=false — returns immediately with a task handle:
{ "task_id": "…", "status": "pending", "workflow": { "…": "…" } }
Query paramRole
waitDefault true — the HTTP call blocks until the run settles.
timeoutHow long to wait, in seconds. Default 120, accepted range 10–300.

With wait=false the response is {"task_id", "status": "pending", "workflow"} — poll the run, or subscribe to its events.

From the SDKs

On your own machine, the published SDKs discover the local agent and run the same workflow with no compute charge:

run.ts

import { WritAgent, runRowId } from "@usewrit/agent-sdk";

const client = new WritAgent();              // discovers the running agent + token
const { data: workflows } = await client.workflows.list();
const run = await client.workflows.runAndWait(workflows[0].id, {
  inputs: { city: "Paris" },
});
const { data: rows } = await client.runs.data(runRowId(run));
console.log(run.status, rows);

Where a run executes decides what it costs: your local agent runs it for free; a cloud run is metered from your plan’s included usage — see billing.

lifecycle ▸ eight statuses

The run lifecycle.

Every run reports one of eight normalized statuses:

StatusMeaning
queuedA cloud run waiting for a slot — it exposes its place in line and an ETA.
pendingDispatched toward a desktop agent and waiting to be picked up. No queue position — the agent pulls when ready.
runningSteps are executing in a live browser.
repairingAI repair is working on the workflow. An overlay state while the repair holds the workflow, not a stored status.
successThe run settled and its outputs are available.
failedThe run settled with an error — the error field says why.
cancelledStopped on request before it settled.
skippedNot executed — for example held back by its own configuration.

queued vs pending: queued is cloud-side (a slot will open; you can see how far back you are). pending is desktop-bound (your agent picks the run up when it connects) — it has no place-in-line to show.

The runs feed unifies five run types in one stream — workflow, check, ai_session, automation and crawl — so everything that executed shows up in one place with the same statuses.

Live events

While a run executes, step-by-step progress streams over SSE — each SDK exposes it in its native idiom:

events.ts

for await (const ev of client.runs.events(runRowId(run))) {
  console.log(ev.type, ev);
}

limits ▸ per plan

How long a run may take.

Every plan sets a maximum run duration. A run that reaches its cap is stopped and settles as failed — it cannot bill open-endedly.

PlanMax run duration
Free2 min
Starter4 min
Pro5 min
Growth10 min
Scale / Enterprise15 min

Cloud recording sessions have their own cap: 10 minutes on Free, up to 60 minutes on Scale and Enterprise. Streaming sessions are capped separately — see the streaming reference.

repair ▸ opt-in ai

AI repair.

Sites change. With ai_repair_enabled on a workflow (off by default), a run that breaks on a stale selector triggers repair instead of just failing. Repair works in two tiers:

Selector repairThe selector is re-derived on the live page; a validated candidate replaces the stale one and the step is retried in place.
Grounded re-recordFor structural changes, a live browser is driven through the flow again and the recipe is re-recorded from what actually works now.

Repair always runs on the managed cloud AI service and is metered by the tokens it uses — it never runs on a BYO key.

While a workflow is repairing it is locked: other queued runs of the same workflow are held until the repair clears, so they don’t all fail on the same broken step.

Each workflow keeps its last 50 repair entries, each tagged repair_type selector or rerecord — you can audit exactly what was changed and why.

Honest failure by default. With the flag off, a broken selector fails the run and says so. There is no silent fallback-selector chain and no non-AI “self-heal” — a run either replays the recipe as recorded, or repair (opted in) fixes it in the open.

next ▸ where to go

Keep going.