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-monitors-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: 3.2.0
info:
title: Writ Agent Monitors 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: Monitors
description: Page monitors (the `targets` table), device check capacity, and change history.
paths:
/v1/monitors:
get:
operationId: listMonitors
tags:
- Monitors
summary: List monitors
description: Newest-first monitor list. **Returns a bare JSON array** (no envelope). Each row is enriched with live check state (`state`, `last_checked_at`, `status_code`, `is_up`, `last_change_at`, `state_updated_at`) plus `changes_count` and `selector_count`. `setup_steps` credentials are redacted to a sentinel.
parameters:
- name: limit
in: query
description: Page size (clamped to 1..=500; default 100).
schema:
type: integer
format: int64
default: 100
- name: check_type
in: query
description: Optional `content` / `uptime` filter.
schema:
type: string
enum:
- content
- uptime
responses:
'200':
description: Bare array of enriched monitors.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Monitor'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createMonitor
tags:
- Monitors
summary: Create a monitor
description: Creates a target. Requires a non-empty `url`. The requested `check_period_ms` is floored to the anti-detection minimum for the chosen path (HTML vs Playwright). An enabled monitor whose cadence would exceed the machine's sustainable checks/min budget is refused with `409 device_capacity`.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorCreate'
responses:
'200':
description: The created monitor row (setup-step credentials redacted; not state-enriched).
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'409':
description: Device check-capacity budget exceeded.
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceCapacityError'
/v1/monitors/capacity:
get:
operationId: getMonitorCapacity
tags:
- Monitors
summary: Device check-capacity meter
description: How much of this machine's sustainable checks/min budget the enabled monitors consume. Device protection, not a billing surface.
responses:
'200':
description: Capacity snapshot.
content:
application/json:
schema:
$ref: '#/components/schemas/CapacitySnapshot'
'401':
$ref: '#/components/responses/Unauthorized'
/v1/monitors/{id}:
parameters:
- $ref: '#/components/parameters/MonitorId'
get:
operationId: getMonitor
tags:
- Monitors
summary: Get one monitor
description: One target, enriched with live check state and counts.
responses:
'200':
description: The enriched monitor.
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
patch:
operationId: updateMonitor
tags:
- Monitors
summary: Update a monitor
description: Partial update (COALESCE semantics — absent fields untouched). Cadence/path changes are re-floored to the anti-detection minimum, and load increases pass through the device-capacity gate (`409 device_capacity` on refusal).
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorUpdate'
responses:
'200':
description: The refreshed monitor row (setup-step credentials redacted; not state-enriched).
content:
application/json:
schema:
$ref: '#/components/schemas/Monitor'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'409':
description: Device check-capacity budget exceeded.
content:
application/json:
schema:
$ref: '#/components/schemas/DeviceCapacityError'
delete:
operationId: deleteMonitor
tags:
- Monitors
summary: Delete a monitor
description: Hard-delete (cascades to selectors, state, changes, uptime samples).
responses:
'200':
description: Deletion acknowledgement.
content:
application/json:
schema:
type: object
required:
- deleted
- id
properties:
deleted:
type: boolean
id:
type: integer
format: int64
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/monitors/{id}/run:
post:
operationId: runMonitor
tags:
- Monitors
summary: Run a monitor check now
description: 'Forces a check regardless of schedule (still respecting the 60s anti-detection floor — a floored attempt returns `status: "skipped"`). Returns the check outcome.'
parameters:
- $ref: '#/components/parameters/MonitorId'
responses:
'200':
description: The per-target check outcome.
content:
application/json:
schema:
$ref: '#/components/schemas/CheckOutcome'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/monitors/{id}/changes:
get:
operationId: listMonitorChanges
tags:
- Monitors
summary: Change + uptime history of a monitor
description: Paginated history carrying BOTH series — content-diff `changes` and up/down + SSL `uptime_checks` — each paged independently against the same `limit`/`offset` window. `has_more` reflects whether either series has rows beyond the page.
parameters:
- $ref: '#/components/parameters/MonitorId'
- name: limit
in: query
description: Page size (clamped to 1..=500; default 100).
schema:
type: integer
format: int64
default: 100
- name: offset
in: query
description: Page offset (default 0).
schema:
type: integer
format: int64
default: 0
responses:
'200':
description: Both history series for the monitor.
content:
application/json:
schema:
$ref: '#/components/schemas/MonitorHistory'
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
/v1/changes/recent:
get:
operationId: listRecentChanges
tags:
- Monitors
summary: Recent changes across all monitors
description: Newest-first detected content changes across ALL monitors, enriched with the monitor URL, selector name and a truncated diff snippet. **Returns a bare JSON array.** Uptime samples are not included.
parameters:
- name: limit
in: query
description: Max entries (clamped to 1..=50; default 10).
schema:
type: integer
format: int64
default: 10
responses:
'200':
description: Bare array of enriched recent changes.
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/RecentChange'
'401':
$ref: '#/components/responses/Unauthorized'
components:
schemas:
CheckOutcome:
type: object
description: 'Outcome of checking a single target (`monitor::CheckOutcome`). Optional fields are OMITTED (not null) when absent. Serialization failure degrades to `{"monitor_id": <id>}`.'
required:
- target_id
- status
properties:
target_id:
type: integer
format: int64
status:
type: string
enum:
- unchanged
- changed
- error
- timeout
- skipped
change_id:
type: integer
format: int64
description: The written `changes.id` (content targets, only when a change was recorded).
dispatch:
$ref: '#/components/schemas/DispatchOutcome'
error:
type: string
description: Human-readable error (only when status is `error`/`timeout`).
MonitorCreate:
type: object
description: Create body (`store::targets::NewTarget`). Flag fields accept booleans or 0/1 integers; numeric fields also accept numeric strings; `setup_steps` / `on_change_conditions` / `notification_providers` / `provider_notification_settings` / `schedule_days` accept a JSON value or a pre-serialized JSON string.
required:
- url
properties:
url:
type: string
check_type:
type:
- string
- 'null'
description: '`content` (default) | `uptime`.'
selector:
type:
- string
- 'null'
ignore_regex:
type:
- string
- 'null'
check_period_ms:
type:
- integer
- string
- 'null'
description: Floored to the anti-detection minimum for the chosen path.
expected_status_code:
type:
- integer
- string
- 'null'
timeout_ms:
type:
- integer
- string
- 'null'
max_response_time_ms:
type:
- integer
- string
- 'null'
check_ssl:
type:
- boolean
- integer
- 'null'
enabled:
type:
- boolean
- integer
- 'null'
requires_playwright:
type:
- boolean
- integer
- 'null'
pre_check_workflow_id:
type:
- integer
- string
- 'null'
setup_steps:
description: '`{steps, credentials}` manifest — JSON object or JSON string.'
on_change_workflow_id:
type:
- integer
- string
- 'null'
on_change_enabled:
type:
- boolean
- integer
- 'null'
on_change_conditions:
description: JSON object or JSON string.
on_change_in_session:
type:
- boolean
- integer
- 'null'
persona_id:
type:
- integer
- string
- 'null'
notification_providers:
description: JSON value or JSON string.
provider_notification_settings:
description: JSON value or JSON string.
notification_title:
type:
- string
- 'null'
notification_message:
type:
- string
- 'null'
notification_priority:
type:
- integer
- string
- 'null'
next_run_at:
type:
- string
- 'null'
schedule_kind:
type:
- string
- 'null'
schedule_time:
type:
- string
- 'null'
schedule_days:
description: ISO weekday ints — JSON array (or JSON string).
schedule_tz:
type:
- string
- 'null'
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
DeviceCapacityError:
description: 'The 409 body for a monitor create/update that would exceed the machine''s sustainable checks/min budget (`code: "device_capacity"`).'
allOf:
- $ref: '#/components/schemas/Error'
- type: object
properties:
would_use_units_per_min:
type: number
budget_units_per_min:
type: number
MonitorUpdate:
type: object
description: Partial PATCH body (`store::targets::TargetUpdate`) — same flexible field shapes as create, everything optional (COALESCE semantics).
properties:
url:
type:
- string
- 'null'
check_type:
type:
- string
- 'null'
selector:
type:
- string
- 'null'
ignore_regex:
type:
- string
- 'null'
check_period_ms:
type:
- integer
- string
- 'null'
expected_status_code:
type:
- integer
- string
- 'null'
timeout_ms:
type:
- integer
- string
- 'null'
max_response_time_ms:
type:
- integer
- string
- 'null'
check_ssl:
type:
- boolean
- integer
- 'null'
enabled:
type:
- boolean
- integer
- 'null'
requires_playwright:
type:
- boolean
- integer
- 'null'
setup_steps:
description: JSON object or JSON string; `"null"`/`""` clears.
on_change_workflow_id:
type:
- integer
- string
- 'null'
on_change_enabled:
type:
- boolean
- integer
- 'null'
on_change_conditions:
description: JSON object or JSON string.
on_change_in_session:
type:
- boolean
- integer
- 'null'
persona_id:
type:
- integer
- string
- 'null'
notification_providers:
description: JSON value or JSON string.
provider_notification_settings:
description: JSON value or JSON string.
notification_title:
type:
- string
- 'null'
notification_message:
type:
- string
- 'null'
notification_priority:
type:
- integer
- string
- 'null'
next_run_at:
type:
- string
- 'null'
schedule_kind:
type:
- string
- 'null'
schedule_time:
type:
- string
- 'null'
schedule_days:
description: ISO weekday ints — JSON array (or JSON string).
schedule_tz:
type:
- string
- 'null'
CapacitySnapshot:
type: object
description: Device check-capacity meter (`scheduler::capacity::CapacitySnapshot`).
required:
- budget_units_per_min
- used_units_per_min
- cores
- ram_gib
- override_units_per_min
properties:
budget_units_per_min:
type: number
used_units_per_min:
type: number
cores:
type: integer
ram_gib:
type: number
override_units_per_min:
type: integer
description: Non-zero when a config override replaces the auto budget.
UptimeCheck:
type: object
description: One up/down + SSL sample (`store::uptime_checks::UptimeCheck`).
required:
- id
- target_id
- checked_at
- is_up
properties:
id:
type: integer
format: int64
target_id:
type: integer
format: int64
checked_at:
type: string
is_up:
type: integer
description: 0/1 integer flag.
status_code:
type:
- integer
- 'null'
response_time_ms:
type:
- integer
- 'null'
format: int64
error_message:
type:
- string
- 'null'
ssl_cert_valid:
type:
- integer
- 'null'
description: 0/1 integer flag.
ssl_cert_expires_at:
type:
- string
- 'null'
ssl_cert_days_until_expiry:
type:
- integer
- 'null'
format: int64
ssl_cert_issuer:
type:
- string
- 'null'
ssl_error:
type:
- string
- 'null'
DispatchOutcome:
type: object
description: Automation dispatch tally for one detected change.
required:
- matched
- succeeded
- failed
properties:
matched:
type: integer
succeeded:
type: integer
failed:
type: integer
RecentChange:
type: object
description: One enriched cross-monitor change (`store::changes::RecentChange`).
required:
- id
- target_id
- target_url
- first_detected_at
- last_detected_at
properties:
id:
type: integer
format: int64
target_id:
type: integer
format: int64
target_url:
type: string
target_selector_id:
type:
- integer
- 'null'
format: int64
selector_name:
type:
- string
- 'null'
diff_snippet:
type:
- string
- 'null'
first_detected_at:
type: string
last_detected_at:
type: string
MonitorHistory:
type: object
description: Paginated change + uptime history (`GET /v1/monitors/{id}/changes`).
required:
- monitor_id
- limit
- offset
- has_more
- changes
- uptime_checks
properties:
monitor_id:
type: integer
format: int64
limit:
type: integer
offset:
type: integer
has_more:
type: boolean
description: Whether EITHER series has rows beyond this page.
changes:
type: array
items:
$ref: '#/components/schemas/Change'
uptime_checks:
type: array
items:
$ref: '#/components/schemas/UptimeCheck'
Change:
type: object
description: One content-diff history row (`store::changes::Change`).
required:
- id
- target_id
- content_hash
- first_detected_at
- last_detected_at
properties:
id:
type: integer
format: int64
target_id:
type: integer
format: int64
target_selector_id:
type:
- integer
- 'null'
format: int64
content_hash:
type: string
previous_hash:
type:
- string
- 'null'
diff_snippet:
type:
- string
- 'null'
content_before:
type:
- string
- 'null'
content_after:
type:
- string
- 'null'
screenshot_before:
type:
- string
- 'null'
screenshot_after:
type:
- string
- 'null'
screenshot_diff:
type:
- string
- 'null'
first_detected_at:
type: string
last_detected_at:
type: string
Monitor:
type: object
description: 'A monitor (`targets` row). `setup_steps` is credential-REDACTED on every response: its `credentials` sub-object is replaced with the sentinel string `«redacted»` (an unparsable stored payload collapses to the bare sentinel string). List/get responses are additionally enriched with live state (`state`, `last_checked_at`, `status_code`, `is_up`, `last_change_at`, `state_updated_at`) and counts (`changes_count`, `selector_count`); create/update responses are the bare row.'
required:
- id
- url
- check_type
- enabled
- requires_playwright
- on_change_enabled
- on_change_in_session
- created_at
properties:
id:
type: integer
format: int64
url:
type: string
check_type:
type: string
description: '`content` | `uptime`.'
selector:
type:
- string
- 'null'
ignore_regex:
type:
- string
- 'null'
check_period_ms:
type:
- integer
- 'null'
format: int64
expected_status_code:
type:
- integer
- 'null'
format: int64
timeout_ms:
type:
- integer
- 'null'
format: int64
max_response_time_ms:
type:
- integer
- 'null'
format: int64
check_ssl:
type:
- integer
- 'null'
description: 0/1 integer flag.
enabled:
type: integer
description: 0/1 integer flag.
requires_playwright:
type: integer
description: 0/1 integer flag (JS/Playwright path vs plain HTML fetch).
baseline_hash:
type:
- string
- 'null'
baseline_content:
type:
- string
- 'null'
baseline_fetched_at:
type:
- string
- 'null'
pre_check_workflow_id:
type:
- integer
- 'null'
format: int64
setup_steps:
description: 'Redacted setup-steps manifest: an object whose `credentials` is the string `«redacted»`, the bare sentinel string, or null.'
type:
- object
- string
- 'null'
on_change_workflow_id:
type:
- integer
- 'null'
format: int64
on_change_enabled:
type: integer
description: 0/1 integer flag.
on_change_conditions:
type:
- string
- 'null'
description: JSON-TEXT (returned as the stored string).
on_change_in_session:
type: integer
description: 0/1 integer flag.
persona_id:
type:
- integer
- 'null'
format: int64
auth_session_encrypted:
type:
- string
- 'null'
description: Sealed session blob (opaque; present as stored).
notification_providers:
type:
- string
- 'null'
provider_notification_settings:
type:
- string
- 'null'
notification_title:
type:
- string
- 'null'
notification_message:
type:
- string
- 'null'
notification_priority:
type:
- integer
- 'null'
format: int64
next_run_at:
type:
- string
- 'null'
schedule_kind:
type:
- string
- 'null'
description: '`interval` (default) | `daily` | `weekly`.'
schedule_time:
type:
- string
- 'null'
schedule_days:
type:
- string
- 'null'
description: JSON array string of ISO weekday ints.
schedule_tz:
type:
- string
- 'null'
created_at:
type: string
updated_at:
type:
- string
- 'null'
state:
type:
- string
- 'null'
description: 'Enrichment (list/get only): live check state (`unchanged`, `changed`, `error`, `checking`, …).'
last_checked_at:
type:
- string
- 'null'
description: Enrichment (list/get only).
status_code:
type:
- integer
- 'null'
description: Enrichment (list/get only).
is_up:
type:
- boolean
- 'null'
description: Enrichment (list/get only).
last_change_at:
type:
- string
- 'null'
description: Enrichment (list/get only).
state_updated_at:
type:
- string
- 'null'
description: Enrichment (list/get only) — the live-state row's own update stamp.
changes_count:
type: integer
description: Enrichment (list/get only).
selector_count:
type: integer
description: Enrichment (list/get only).
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'
BadRequest:
description: 'Invalid input. Domain validation errors use the JSON Error shape; axum body-deserialize rejections are `text/plain` with a bare message (e.g. `Failed to deserialize the JSON body into the target type: missing field ''url''`).'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
text/plain:
schema:
type: string
parameters:
MonitorId:
name: id
in: path
required: true
description: Monitor (target) row id.
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"