usewrit-app Runs API

Read-only run feed, per-run results/data, live SSE events, and cancel-by-run-id.

Operations 6

GET /v1/runs List runs (enriched feed) #
GET /v1/runs/{id} Get one run #
GET /v1/runs/{id}/results Raw run result payload #
GET /v1/runs/{id}/data Extracted data of one run #
GET /v1/runs/{id}/events Live run event stream (SSE) #
POST /v1/runs/{id}/cancel Cancel a live run by run id #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/usewrit-app:usewrit-app-runs-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

usewrit-app-runs-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Writ Agent Runs API
  version: 0.1.0
  license:
    name: MIT
    identifier: MIT
  description: 'Loopback HTTP API of the Writ local agent (`writ-agentd`), the daemon embedded in the

    Writ desktop app and the self-host build. This document is generated against the Rust

    daemon source (`writ-agent/src/local/`), which is the wire truth.


    ## Transport


    - Plain HTTP on `127.0.0.1:8131` (default; `WRIT_PORT` env overrides).

    - Optional HTTPS twin on `127.0.0.1:8132`, served with a certificate minted by a

    per-install, name-constrained local CA (`~/.writ/tls/ca.pem`). Clients must trust that

    CA file (or pin it) to use the TLS port; the payloads are identical on both ports.

    - The daemon enforces a DNS-rebind guard: `Host`/`Origin` must be loopback. Talk to

    `127.0.0.1`, not `localhost`.


    ## Errors


    Domain errors are JSON `{"error": "", "code": ""}` (see the

    `Error` schema). Codes include `bad_request`, `unauthorized`, `forbidden`, `not_found`,

    `vault_locked` (HTTP 423), `too_many_requests`, `device_capacity` (HTTP 409),

    `captcha_required` (HTTP 402), `internal`.


    **Caveat:** some 4xx bodies are `text/plain`, not JSON — axum framework rejections

    (e.g. a JSON body that fails to deserialize returns

    `Failed to deserialize the JSON body into the target type: missing field ''url''` as

    plain text with a 4xx status). Clients must tolerate non-JSON error bodies.


    ## List envelopes


    The wire is deliberately documented as-is (SDKs normalize; this spec does not):


    - `{"data": [...], "count": n}` — workflows, personas, persona runs, secrets, files, keys

    - `{"data": [...], "count": n, "total": n}` — runs

    - **bare JSON array** — monitors, monitor selectors, extractors, automations,

    `/v1/changes/recent`

    - bespoke objects — `/v1/monitors/{id}/changes`, `/v1/data`, the workflow-data table'
servers:
- url: http://127.0.0.1:8131
  description: Plain-HTTP loopback listener (default port; WRIT_PORT overrides).
- url: https://127.0.0.1:8132
  description: HTTPS twin of the same API, terminated with a certificate from the per-install local CA at ~/.writ/tls/ca.pem. Trust that CA (or pin it) in your HTTP client.
security:
- bearerAuth: []
tags:
- name: Runs
  description: Read-only run feed, per-run results/data, live SSE events, and cancel-by-run-id.
paths:
  /v1/runs:
    get:
      operationId: listRuns
      tags:
      - Runs
      summary: List runs (enriched feed)
      description: Newest-first run feed in the `{data, count, total}` envelope (`total` is the post-filter total before pagination; `count` is the page length). Each item is an enriched `RunFeedItem` projection, not the raw row. Without a `run_type` filter, successful monitor checks are suppressed (only failed checks surface in the unified feed).
      parameters:
      - name: entity_id
        in: query
        description: Scope to one entity, interpreted per `run_type` (workflow_id / target_id / automation_id). Aliases `workflow_id` for workflow-type queries.
        schema:
          type: integer
          format: int64
      - name: workflow_id
        in: query
        description: Legacy alias for `entity_id` on workflow-type queries.
        schema:
          type: integer
          format: int64
      - name: run_type
        in: query
        description: Filter by feed run type. Only workflow runs persist locally today, so `check`/`automation` yield an empty page.
        schema:
          type: string
          enum:
          - workflow
          - check
          - automation
      - name: status
        in: query
        description: Filter by run status (e.g. `running`, `success`, `failed`).
        schema:
          type: string
      - name: limit
        in: query
        description: Page size (clamped to 1..=1000; default 100).
        schema:
          type: integer
          format: int64
          default: 100
      - name: offset
        in: query
        description: Pagination offset (applied after scoping/projection).
        schema:
          type: integer
          format: int64
          default: 0
      responses:
        '200':
          description: Run feed page in the `{data, count, total}` envelope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunList'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /v1/runs/{id}:
    get:
      operationId: getRun
      tags:
      - Runs
      summary: Get one run
      description: One run as the enriched `RunFeedItem` projection (same shape as the list). The path id is the NUMERIC run row id — the part after the last dash of the feed item's composite string `id`.
      parameters:
      - $ref: '#/components/parameters/RunId'
      responses:
        '200':
          description: The enriched run feed item.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunFeedItem'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/runs/{id}/results:
    get:
      operationId: getRunResults
      tags:
      - Runs
      summary: Raw run result payload
      description: The run's raw `result_data` JSON. `result` is `null` when the run produced no payload (still running, or none written).
      parameters:
      - $ref: '#/components/parameters/RunId'
      responses:
        '200':
          description: Run result envelope.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunResults'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/runs/{id}/data:
    get:
      operationId: getRunData
      tags:
      - Runs
      summary: Extracted data of one run
      description: The run's extracted output (the `extracted_data` key of `result_data`, falling back to the whole result payload). With `?format=csv` the response is a CSV attachment (`text/csv`) flattening the run's records — one row per extracted record.
      parameters:
      - $ref: '#/components/parameters/RunId'
      - name: format
        in: query
        description: '`json` (default) or `csv`.'
        schema:
          type: string
          enum:
          - json
          - csv
          default: json
      responses:
        '200':
          description: Extracted data (JSON envelope, or CSV when `?format=csv`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunData'
            text/csv:
              schema:
                type: string
                description: Flattened per-record CSV (run metadata columns + data columns).
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/runs/{id}/events:
    get:
      operationId: streamRunEvents
      tags:
      - Runs
      summary: Live run event stream (SSE)
      description: 'Server-Sent Events (`text/event-stream`) stream of the run''s lifecycle.


        - Frames are **named** SSE events (`event: ` + `data: `) with name one

        of `started | step | progress | finished | error`. The JSON payload also carries

        the same discriminant in an `"event"` field (serde tag), so parsing `data` alone

        is sufficient.

        - `finished` and `error` are **stream-closing** — the connection ends after one of

        them is delivered.

        - Keep-alive **comment frames** (lines starting with `:`) are emitted every 15

        seconds and must be ignored by parsers.

        - A run that is already finished (or not live in the engine registry) yields exactly

        one synthetic terminal frame, then the stream closes immediately.

        - A live subscription is seeded with a synthetic `started` frame

        (`total_steps` may be 0 in that seed) since the broadcast channel does not replay.'
      parameters:
      - $ref: '#/components/parameters/RunId'
      responses:
        '200':
          description: SSE stream. Each event's `data:` line is one JSON document matching the `RunEvent` schema (tagged by its `event` field).
          content:
            text/event-stream:
              schema:
                $ref: '#/components/schemas/RunEvent'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/runs/{id}/cancel:
    post:
      operationId: cancelRun
      tags:
      - Runs
      summary: Cancel a live run by run id
      description: 'Cancels any run row directly (including ad-hoc runs with no workflow). Returns `202 {run_id, status: "cancel_requested"}` when a live run was signalled, or `409 {status: "not_running"}` when the run is already terminal or the engine holds no live handle. The 409 is a valid domain answer, not a failure.'
      parameters:
      - $ref: '#/components/parameters/RunId'
      responses:
        '202':
          description: The live run was signalled to cancel.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunCancelAccepted'
        '409':
          description: The run is not in a cancellable state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RunCancelConflict'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  schemas:
    RunEvent:
      description: One SSE frame's JSON payload, tagged by its `event` field (which also matches the SSE `event:` name). `finished` and `error` are stream-closing.
      oneOf:
      - $ref: '#/components/schemas/RunEventStarted'
      - $ref: '#/components/schemas/RunEventStep'
      - $ref: '#/components/schemas/RunEventProgress'
      - $ref: '#/components/schemas/RunEventFinished'
      - $ref: '#/components/schemas/RunEventError'
      discriminator:
        propertyName: event
        mapping:
          started: '#/components/schemas/RunEventStarted'
          step: '#/components/schemas/RunEventStep'
          progress: '#/components/schemas/RunEventProgress'
          finished: '#/components/schemas/RunEventFinished'
          error: '#/components/schemas/RunEventError'
    RunEventStarted:
      type: object
      description: The run is registered and about to drive its steps. `total_steps` counts the raw steps the engine will iterate (a synthetic subscription seed may report 0).
      required:
      - event
      - run_id
      - total_steps
      properties:
        event:
          type: string
          const: started
        run_id:
          type: integer
          format: int64
        total_steps:
          type: integer
    RunList:
      type: object
      description: '`{data, count, total}` list envelope (runs only).'
      required:
      - data
      - count
      - total
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RunFeedItem'
        count:
          type: integer
          description: Number of items in this page.
        total:
          type: integer
          description: Post-filter total before pagination.
    Error:
      type: object
      description: 'The daemon''s domain error body. NOTE: some 4xx responses (axum framework rejections) are `text/plain` instead of this JSON shape. Certain codes attach extra fields (e.g. `device_capacity` adds `would_use_units_per_min` / `budget_units_per_min`; `captcha_required` adds `captcha_type` / `url`).'
      required:
      - error
      - code
      properties:
        error:
          type: string
          description: 'Human-readable message (e.g. `not found: workflow 999999`).'
        code:
          type: string
          description: 'Stable machine code: `bad_request`, `unauthorized`, `forbidden`, `not_found`, `vault_locked`, `too_many_requests`, `captcha_required`, `device_capacity`, `cloud_unreachable`, `db_error`, `io_error`, `json_error`, `internal`, …'
      additionalProperties: true
    RunCancelAccepted:
      type: object
      description: 202 body of `POST /v1/runs/{id}/cancel`.
      required:
      - run_id
      - status
      properties:
        run_id:
          type: integer
          format: int64
        status:
          type: string
          const: cancel_requested
    RunFeedItem:
      type: object
      description: Enriched run-feed projection (`api/v1/runs.rs::RunFeedItem`). `id` is a COMPOSITE STRING `"<run_type>-<row_id>"` (e.g. `"workflow-3"`); the numeric id for get/cancel/events is the part after the last dash.
      required:
      - id
      - run_type
      - entity_id
      - entity_name
      - status
      - started_at
      - finished_at
      - duration_ms
      - trigger_source
      - error
      - detail_url_hint
      - data_url_hint
      - rows_extracted
      - change_detected
      properties:
        id:
          type: string
          description: 'Composite feed id: `"<run_type>-<row_id>"`.'
        run_type:
          type: string
          enum:
          - workflow
          - check
          - automation
        entity_id:
          type:
          - integer
          - 'null'
          format: int64
          description: The associated workflow / target / automation id, per `run_type`.
        entity_name:
          type:
          - string
          - 'null'
          description: Resolved entity name (workflow runs; `Run <id>` fallback).
        status:
          type: string
          description: 'Open string enum. Known values: `running`, `success`, `failed`, `cancelled`, `timeout`, `captcha_required`, `twofa_required`, `interrupted`.'
        started_at:
          type:
          - string
          - 'null'
          description: RFC3339; falls back to the row's created time.
        finished_at:
          type:
          - string
          - 'null'
        duration_ms:
          type:
          - integer
          - 'null'
          format: int64
        trigger_source:
          type:
          - string
          - 'null'
          description: The run's trigger type (`manual`, `schedule`, …).
        error:
          type:
          - string
          - 'null'
          description: The failure message for failed runs.
        detail_url_hint:
          type:
          - string
          - 'null'
          description: Frontend deep-link to the owning entity (e.g. `/workflows/3`).
        data_url_hint:
          type:
          - string
          - 'null'
          description: Deep-link to the Data view — present only for successful, row-producing, non-streaming workflow runs.
        rows_extracted:
          type:
          - integer
          - 'null'
          format: int64
          description: Workflow runs only — extracted record count; null for other types.
        change_detected:
          type:
          - boolean
          - 'null'
          description: Check runs only — whether the check detected a change; null otherwise.
        engine:
          type: string
          description: Which lane ran this (`http` | `browser` | `hybrid`), from the result payload. OMITTED (not null) for non-workflow runs and older results.
    RunData:
      type: object
      description: Extracted-data envelope (JSON lane). `data` is the `extracted_data` value of the result payload, falling back to the whole result.
      required:
      - run_id
      - status
      - data
      properties:
        run_id:
          type: integer
          format: int64
        status:
          type: string
        data:
          description: The extracted data (any JSON value; null when none).
    RunCancelConflict:
      type: object
      description: 409 body of `POST /v1/runs/{id}/cancel` — the run is terminal or has no live engine handle. `run_status` (the row's actual status) is present only on the already-terminal arm.
      required:
      - run_id
      - status
      properties:
        run_id:
          type: integer
          format: int64
        status:
          type: string
          const: not_running
        run_status:
          type: string
          description: The run row's actual status (present when the row is already terminal).
    RunEventProgress:
      type: object
      description: Coarse progress hint — `completed` of `total` steps done.
      required:
      - event
      - run_id
      - completed
      - total
      properties:
        event:
          type: string
          const: progress
        run_id:
          type: integer
          format: int64
        completed:
          type: integer
        total:
          type: integer
    RunStatus:
      type: string
      description: Terminal/in-flight run status (serde snake_case of `engine::RunStatus`). Treat as an open string enum in clients.
      enum:
      - running
      - success
      - failed
      - cancelled
      - timeout
      - captcha_required
      - twofa_required
    RunEventError:
      type: object
      description: 'TERMINAL: the run failed around the step loop. `message` is an operator-facing classification, never a raw secret/value. Stream-closing.'
      required:
      - event
      - run_id
      - message
      properties:
        event:
          type: string
          const: error
        run_id:
          type: integer
          format: int64
        message:
          type: string
    RunEventFinished:
      type: object
      description: 'TERMINAL: the run reached a final status. Stream-closing.'
      required:
      - event
      - run_id
      - status
      properties:
        event:
          type: string
          const: finished
        run_id:
          type: integer
          format: int64
        status:
          $ref: '#/components/schemas/RunStatus'
    RunEventStep:
      type: object
      description: A step transitioned. `index` is 0-based into the raw step list; `step_type` is the recorded step kind — never a value.
      required:
      - event
      - run_id
      - index
      - step_type
      - status
      properties:
        event:
          type: string
          const: step
        run_id:
          type: integer
          format: int64
        index:
          type: integer
        step_type:
          type: string
        status:
          $ref: '#/components/schemas/StepStatus'
    StepStatus:
      type: string
      description: Coarse per-step lifecycle marker.
      enum:
      - running
      - succeeded
      - failed
      - skipped
    RunResults:
      type: object
      description: Raw run result envelope.
      required:
      - run_id
      - status
      - result
      properties:
        run_id:
          type: integer
          format: int64
        status:
          type: string
        result:
          description: The parsed `result_data` payload; null when none exists.
  responses:
    NotFound:
      description: The addressed resource does not exist.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: Missing/invalid bearer token, or the Origin/Host guard rejected the request.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  parameters:
    RunId:
      name: id
      in: path
      required: true
      description: Numeric run row id (for feed items with a composite string id like `workflow-3`, this is the part after the last dash).
      schema:
        type: integer
        format: int64
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: "Every route (including `/v1/agent` and `/v1/health`) requires\n`Authorization: Bearer <token>`. Token families:\n\n- `wlt_…` — the runtime token from `runtime.json` (full access, used by the desktop\n  UI and SDK discovery).\n- `wlk_…` — a scoped API key minted via `POST /v1/keys` (CSV scopes `read|run|admin`;\n  `/v1/keys` itself requires the full-access token).\n- `wlo_…` — an OAuth 2.1 access token from the daemon's local authorization server\n  (`run` scope).\n\nTreat the token as an opaque string. The daemon additionally enforces a loopback\nOrigin/Host DNS-rebind guard.\n"