Internal REST API
When Probe starts, it spins up an internal HTTP server on 127.0.0.1:<random> so probe-mcp and other local processes can read traffic, manage rules, and replay requests. The same API is documented here in case you want to script against Probe from a shell, a CI job, or your own tooling.
The server is implemented with axum and lives next to the proxy in the Rust crate.
Discovery — handshake file
Section titled “Discovery — handshake file”Probe writes the listening port and a freshly-generated bearer token to:
~/.probe/api-handshake.json (macOS)%USERPROFILE%\.probe\api-handshake.json (Windows)Shape:
{ "port": 51827, "token": "f3b1...c2a9", "pid": 73114, "version": "1.4.0", "started_at": 1731081822}- The file is written with mode
0600on Unix. - A fresh token is generated each Probe launch — restarts invalidate previous tokens.
- The file is removed when Probe quits cleanly.
Read it once, point your client at http://127.0.0.1:<port>/api/v1/..., send the token in the Authorization header.
Authentication
Section titled “Authentication”Every endpoint except /api/v1/health requires:
Authorization: Bearer <token>Without the header (or with the wrong token), the API returns 401 Unauthorized.
Quick check:
PORT=$(jq -r .port ~/.probe/api-handshake.json)TOKEN=$(jq -r .token ~/.probe/api-handshake.json)curl -s "http://127.0.0.1:$PORT/api/v1/health"# {"status":"ok","version":"1.4.0","capturing":true,"entry_count":42, ...}Health
Section titled “Health”GET /api/v1/health
Section titled “GET /api/v1/health”Public — no auth required. Useful for liveness checks.
{ "status": "ok", "version": "1.4.0", "capturing": true, "entry_count": 142, "uptime_secs": 3621, "api_port": 51827}Traffic
Section titled “Traffic”GET /api/v1/traffic
Section titled “GET /api/v1/traffic”List captured entries with optional filters.
| Query param | Type | Description |
|---|---|---|
limit | int | Default 50, capped at 500. |
offset | int | Default 0. |
host | string | Substring match against host. |
method | string | Exact match. |
status | int | Exact status code (sets both min and max). |
status_min / status_max | int | Status range. |
path | string | URL path filter. |
search | string | Substring search across URL and body. |
has_error | bool | true for status ≥ 400 only. |
since_id | int | Only entries with id > since_id. |
since | int | Only entries with timestamp >= since (ms since epoch). |
curl -s "http://127.0.0.1:$PORT/api/v1/traffic?host=api.example.com&limit=10" \ -H "Authorization: Bearer $TOKEN"Response:
{ "items": [ { "id": 142, "method": "POST", "url": "https://api.example.com/v1/users", "host": "api.example.com", "status_code": 201, "content_type": "application/json", "duration_ms": 184, "timestamp": 1731081900123, "client_addr": "Chrome", "has_response": true } ], "total": 142, "offset": 0, "limit": 10}GET /api/v1/traffic/{id}
Section titled “GET /api/v1/traffic/{id}”Full detail of one entry — including request/response headers and bodies.
{ "id": 142, "method": "POST", "url": "https://api.example.com/v1/users", "host": "api.example.com", "status_code": 201, "timestamp": 1731081900123, "req_headers": [["content-type", "application/json"], ["authorization", "Bearer ..."]], "req_body": "{\"name\":\"Ada\"}", "res_headers": [["content-type", "application/json"]], "res_body": "{\"id\":1,\"name\":\"Ada\"}", "content_type": "application/json", "client_addr": "Chrome", "duration_ms": 184, "has_response": true}404 if the id doesn’t exist.
DELETE /api/v1/traffic
Section titled “DELETE /api/v1/traffic”Drop every entry. Returns 204 No Content.
DELETE /api/v1/traffic/{id}
Section titled “DELETE /api/v1/traffic/{id}”Drop a single entry. 204 on success, 404 if missing.
GET /api/v1/traffic/{id}/export/curl
Section titled “GET /api/v1/traffic/{id}/export/curl”Plain text — a runnable cURL command. Content-Type: text/plain.
GET /api/v1/traffic/{id}/export/raw
Section titled “GET /api/v1/traffic/{id}/export/raw”Plain text — raw HTTP request and response.
POST /api/v1/traffic/{id}/replay
Section titled “POST /api/v1/traffic/{id}/replay”Replay a captured request. Body is {} (no parameters today). Response carries the new entry’s status, headers, and body. The replay also lands in the live traffic list as a new entry.
GET /api/v1/traffic/{id1}/diff/{id2}
Section titled “GET /api/v1/traffic/{id1}/diff/{id2}”Compare two entries. Returns a structured diff over method, URL, status, headers, and body.
Capture control
Section titled “Capture control”GET /api/v1/capture/status
Section titled “GET /api/v1/capture/status”{ "capturing": true, "entry_count": 142 }POST /api/v1/capture/start
Section titled “POST /api/v1/capture/start”Enable recording. Returns 204.
POST /api/v1/capture/stop
Section titled “POST /api/v1/capture/stop”Disable recording — traffic still proxies, it just isn’t stored. Returns 204.
Breakpoints
Section titled “Breakpoints”GET /api/v1/breakpoints
Section titled “GET /api/v1/breakpoints”Array of {pattern, intercept_request, intercept_response}.
PUT /api/v1/breakpoints
Section titled “PUT /api/v1/breakpoints”Create or update a breakpoint. Pattern goes in the body so URL-shaped patterns (with slashes) don’t need re-encoding.
{ "pattern": "api.example.com/users/*", "intercept_request": true, "intercept_response": true }204 No Content.
POST /api/v1/breakpoints/delete
Section titled “POST /api/v1/breakpoints/delete”Delete a single breakpoint by pattern.
{ "pattern": "api.example.com/users/*" }DELETE /api/v1/breakpoints
Section titled “DELETE /api/v1/breakpoints”Drop every breakpoint. 204.
Intercepts
Section titled “Intercepts”A breakpoint or intercept-host match parks the request until you resume it.
GET /api/v1/intercepts
Section titled “GET /api/v1/intercepts”Array of {id} — one per paused request.
POST /api/v1/intercepts/{id}/resume
Section titled “POST /api/v1/intercepts/{id}/resume”Release a paused request, optionally rewriting it.
{ "abort": false, "status_code": 200, "headers": [["x-debug", "1"]], "body": "{\"forced\":true}", "url": "https://staging.example.com/v1/users"}204 on success, 404 if the id no longer exists.
Ignored hosts
Section titled “Ignored hosts”GET /api/v1/ignored-hosts
Section titled “GET /api/v1/ignored-hosts”Array of host strings.
PUT /api/v1/ignored-hosts/{host}
Section titled “PUT /api/v1/ignored-hosts/{host}”Add a host. 204.
DELETE /api/v1/ignored-hosts/{host}
Section titled “DELETE /api/v1/ignored-hosts/{host}”Remove a host. 204.
DELETE /api/v1/ignored-hosts
Section titled “DELETE /api/v1/ignored-hosts”Clear the list. 204.
Ignored clients
Section titled “Ignored clients”Same shape as ignored hosts, but the key is a client label — process name (loopback) or IP (remote phone).
GET /api/v1/ignored-clientsPUT /api/v1/ignored-clients/{label}DELETE /api/v1/ignored-clients/{label}
The label segment is URL-encoded; the server decodes it.
Intercept hosts
Section titled “Intercept hosts”Hosts that pause matching requests for inspection (a coarser form of breakpoint).
GET /api/v1/intercept-hostsPUT /api/v1/intercept-hosts/{host}DELETE /api/v1/intercept-hosts/{host}
Throttle
Section titled “Throttle”GET /api/v1/throttle
Section titled “GET /api/v1/throttle”Current global throttle state.
PUT /api/v1/throttle
Section titled “PUT /api/v1/throttle”Set the global bandwidth cap.
{ "bytes_per_second": 102400 }DELETE /api/v1/throttle
Section titled “DELETE /api/v1/throttle”Disable the global throttle.
GET /api/v1/throttle-rules
Section titled “GET /api/v1/throttle-rules”List the scoped throttle rules currently loaded by the proxy. Read-only at this layer — Probe’s UI owns persistence and pushes the list on every mutation.
Sessions
Section titled “Sessions”GET /api/v1/session-tabs
Section titled “GET /api/v1/session-tabs”Active session tabs in the running app.
GET /api/v1/session-tabs/{id}
Section titled “GET /api/v1/session-tabs/{id}”Entries inside a specific tab.
GET /api/v1/session-tabs/saved
Section titled “GET /api/v1/session-tabs/saved”Sessions persisted to disk under ~/.probe/sessions/.
GET /api/v1/session-tabs/saved/{id}
Section titled “GET /api/v1/session-tabs/saved/{id}”Entries inside a saved session.
Compose
Section titled “Compose”GET /api/v1/compose/collections
Section titled “GET /api/v1/compose/collections”Array of {id, name, request_count, folder_count, environment_count, created_at}.
GET /api/v1/compose/collections/{id}
Section titled “GET /api/v1/compose/collections/{id}”Full collection JSON — folders, requests, environments, default headers, default auth.
POST /api/v1/compose/collections
Section titled “POST /api/v1/compose/collections”Create a collection.
{ "name": "Acme API", "requests": [ { "name": "List users", "method": "GET", "url": "{{base}}/users" } ], "folders": [], "environments": [ { "name": "Dev", "variables": [{"key": "base", "defaultValue": "https://dev.acme.test"}] } ]}Returns the persisted collection (with server-assigned id).
POST /api/v1/compose/collections/{id}/requests
Section titled “POST /api/v1/compose/collections/{id}/requests”Add a request to a collection. Optional folder_id to nest it.
{ "folder_id": null, "request": { "name": "Create user", "method": "POST", "url": "{{base}}/users", "headers": [ { "key": "Content-Type", "value": "application/json", "enabled": true } ], "queryParams": [], "body": "{\"name\":\"Ada\",\"email\":\"ada@example.com\"}", "contentType": "application/json", "auth": null, "timeout": 30000 }}auth is null to inherit from the collection, or a typed object: {"type": "bearer", "token": "..."}, {"type": "basic", "username": "...", "password": "..."}, {"type": "apiKey", "key": "...", "value": "..."}. Returns the persisted request with its server-assigned id.
POST /api/v1/compose/collections/{id}/environments
Section titled “POST /api/v1/compose/collections/{id}/environments”Add an environment.
{ "name": "Dev", "variables": [ { "key": "base", "defaultValue": "https://dev.acme.test", "currentValue": null, "enabled": true, "isSecret": false }, { "key": "token", "defaultValue": "", "currentValue": "abc123", "enabled": true, "isSecret": true } ]}| Field | Type | Notes |
|---|---|---|
key | string | Variable name; referenced as {{key}} in URL/headers/body. |
defaultValue | string | Persisted value. |
currentValue | string? | Session-only override. Cleared on app restart. |
enabled | bool | When false, the variable behaves as if undefined. |
isSecret | bool | Masks the value in console output and the variable hover preview. |
POST /api/v1/compose/send
Section titled “POST /api/v1/compose/send”Resolve variables and send a saved request through Probe.
{ "collection_id": "abc", "request_id": "xyz", "environment": "Dev", "variables": { "userId": "42" }}Returns the response (status, headers, body, duration_ms).
Map Local
Section titled “Map Local”GET /api/v1/map-local
Section titled “GET /api/v1/map-local”State snapshot — the master enable flag and every rule.
GET /api/v1/map-local/match?method=GET&url=https://...
Section titled “GET /api/v1/map-local/match?method=GET&url=https://...”Preview which rule (if any) would intercept the given URL.
{ "matched": true, "rule": { "id": "...", "url_pattern": "..." } }PUT /api/v1/map-local/enabled
Section titled “PUT /api/v1/map-local/enabled”{ "enabled": true }POST /api/v1/map-local/rules
Section titled “POST /api/v1/map-local/rules”Create a rule.
{ "id": "rule-1", "url_pattern": "api.example.com/users/*", "method": "GET", "match_all_methods": false, "enabled": true, "source": "inline_body", "inline_body": "{\"id\":1,\"name\":\"Ada\"}", "inline_format": "application/json", "status_code": 200, "response_headers": { "x-mocked-by": "probe" }, "delay_ms": 0}| Field | Type | Notes |
|---|---|---|
id | string | Caller-supplied; pick any stable value. |
url_pattern | string | Glob (*, **). Default :443/:80 is stripped before matching. |
method | string | Method to match. Ignored when match_all_methods is true. |
match_all_methods | bool | When true, method is ignored. Default false. |
enabled | bool | Per-rule toggle. Default true. |
source | string | "inline_body" or "local_file". Anything else falls back to inline. |
file_path | string? | Required when source = "local_file". Absolute path on the host running Probe. |
inline_body | string? | Used when source = "inline_body". Raw bytes (UTF-8 or base64-encoded for binary). |
inline_format | string? | MIME hint (application/json, text/html, image/png, …). Used to set Content-Type on the served response. |
status_code | int | HTTP status to return. Default 200. |
response_headers | object | Extra response headers. |
delay_ms | int | Artificial delay before responding. Default 0. |
Returns the persisted rule.
PUT /api/v1/map-local/rules/{id}
Section titled “PUT /api/v1/map-local/rules/{id}”Replace a rule. Body shape identical to POST.
DELETE /api/v1/map-local/rules/{id}
Section titled “DELETE /api/v1/map-local/rules/{id}”Delete by id. 204.
POST /api/v1/map-local/rules/{id}/toggle
Section titled “POST /api/v1/map-local/rules/{id}/toggle”Flip the rule’s enabled flag. Returns the updated rule.
Map Remote
Section titled “Map Remote”Same endpoint shape as Map Local under /api/v1/map-remote. The rule body differs — it splits source-match criteria from destination-rewrite fields.
GET /api/v1/map-remote
Section titled “GET /api/v1/map-remote”State snapshot — the master enable flag and every rule.
GET /api/v1/map-remote/match?method=GET&url=https://...
Section titled “GET /api/v1/map-remote/match?method=GET&url=https://...”Preview which rule (if any) would rewrite the URL. Returns the matched rule and the rewritten destination URL.
PUT /api/v1/map-remote/enabled
Section titled “PUT /api/v1/map-remote/enabled”{ "enabled": true }POST /api/v1/map-remote/rules
Section titled “POST /api/v1/map-remote/rules”Create a rule.
{ "id": "rule-1", "enabled": true, "source_scheme": "https", "source_host": "api.production.com", "source_port": null, "source_path": "/v1/*", "method": "GET", "match_all_methods": true, "dest_scheme": "https", "dest_host": "staging.example.com", "dest_port": null, "dest_path": "/v1/$1", "preserve_original_host_header": false}| Field | Type | Notes |
|---|---|---|
id | string | Caller-supplied. |
enabled | bool | Default true. |
source_scheme | string | http or https. |
source_host | string | Host to match (no port). |
source_port | int? | null means scheme default (443/80). |
source_path | string | Path glob. * captures one segment substring (regex .*, anchored). Captures are addressable in dest_path as $1, $2, … in order. |
method | string | Method to match. Ignored when match_all_methods is true. |
match_all_methods | bool | Default false. |
dest_scheme | string | http or https. |
dest_host | string | Rewrite target host. |
dest_port | int? | null means scheme default. |
dest_path | string | Rewritten path. Use $1, $2, … to substitute captures from source_path. |
preserve_original_host_header | bool | When true, the rewritten request keeps the original Host header — useful for API gateways that route by Host. Default false. |
Returns the persisted rule.
PUT /api/v1/map-remote/rules/{id}
Section titled “PUT /api/v1/map-remote/rules/{id}”Replace a rule. Body shape identical to POST.
DELETE /api/v1/map-remote/rules/{id}
Section titled “DELETE /api/v1/map-remote/rules/{id}”Delete by id. 204.
POST /api/v1/map-remote/rules/{id}/toggle
Section titled “POST /api/v1/map-remote/rules/{id}/toggle”Flip enabled. Returns the updated rule.
Mock Server
Section titled “Mock Server”The Mock Server is a standalone HTTP listener (default port 9100) that serves canned responses to clients that hit it directly — no proxying involved. It’s the third consumer of the same rule-list plumbing as Map Local and Map Remote, with its own listener lifecycle on top.
GET /api/v1/mock
Section titled “GET /api/v1/mock”State snapshot — the master enable flag and every rule.
GET /api/v1/mock/status
Section titled “GET /api/v1/mock/status”Listener state.
{ "running": true, "port": 9100 }GET /api/v1/mock/match?method=GET&path=/x
Section titled “GET /api/v1/mock/match?method=GET&path=/x”Preview which rule (if any) would serve the given method + path.
{ "matched": true, "rule": { "id": "...", "url_pattern": "..." } }PUT /api/v1/mock/enabled
Section titled “PUT /api/v1/mock/enabled”Master toggle. When false, every request gets the 404 fallback.
{ "enabled": true }POST /api/v1/mock/start
Section titled “POST /api/v1/mock/start”Start the listener.
{ "port": 9100 }409 Conflict if the port is already in use, or the listener is already running.
POST /api/v1/mock/stop
Section titled “POST /api/v1/mock/stop”Stop the listener. 204 No Content.
POST /api/v1/mock/rules
Section titled “POST /api/v1/mock/rules”Create a rule.
{ "id": "rule-1", "name": "Users list", "enabled": true, "method": "GET", "url_pattern": "/api/users/*", "status_code": 200, "status_text": null, "headers": [["content-type", "application/json"]], "body": "{\"id\":1,\"name\":\"Ada\"}", "body_is_base64": false, "latency_ms": 0}| Field | Type | Notes |
|---|---|---|
id | string | Caller-supplied; pick any stable value. |
name | string | Display name in the manager. |
enabled | bool | Per-rule toggle. |
method | string | Method to match. Empty string matches any method. |
url_pattern | string | A path, not a full URL (e.g. /api/users/*). * is a wildcard. A pattern without ? ignores the request’s query string. First enabled rule wins. |
status_code | int | Default 200. |
status_text | string? | Optional custom reason phrase. |
headers | array of [name, value] | Response headers. |
body | string | Response body. |
body_is_base64 | bool | When true, body is decoded from base64 to raw bytes — for binary responses. Default false. |
latency_ms | int? | Optional artificial delay before responding. |
Returns the persisted rule.
PUT /api/v1/mock/rules/{id}
Section titled “PUT /api/v1/mock/rules/{id}”Replace a rule. Body shape identical to POST.
DELETE /api/v1/mock/rules/{id}
Section titled “DELETE /api/v1/mock/rules/{id}”Delete by id. 204.
POST /api/v1/mock/rules/{id}/toggle
Section titled “POST /api/v1/mock/rules/{id}/toggle”Flip the rule’s enabled flag. Returns the updated rule.
Files (remote-fs)
Section titled “Files (remote-fs)”Routes for the Files feature — SFTP, FTP/FTPS, S3, and WebDAV browsing and transfers. They drive the same command/event engine the Flutter Files UI uses, via a synchronous request/reply layer bolted onto that engine (the streaming FRB/UI path is untouched).
There is no connect route by design: credentials (passwords, S3 keys) cross the FRB bridge once, from the UI, and are never persisted in Rust. This surface only operates on connections the user has already opened in the app — list them, then drive file ops and transfers against their ids.
GET /api/v1/remote-fs/connections
Section titled “GET /api/v1/remote-fs/connections”Snapshot of every open connection.
{ "connections": [ { "id": "conn-1", "protocol": "sftp", "host": "example.com", "label": "Prod box", "homePath": "/home/user", "encrypted": true } ]}GET /api/v1/remote-fs/transfers
Section titled “GET /api/v1/remote-fs/transfers”Snapshot of the transfer queue.
{ "transfers": [ { "transferId": "tr_rest_...", "connectionId": "conn-1", "kind": "download", "state": "running", "remotePath": "/var/www/app.log", "localPath": "/Users/me/Downloads/app.log", "bytesDone": 40960 } ]}state is one of queued/running/paused/done/cancelled/error.
POST /api/v1/remote-fs/connections/{id}/list
Section titled “POST /api/v1/remote-fs/connections/{id}/list”{ "path": "/var/www" }Returns {path, entries} — each entry carries name, path, isDir, size, and (when the backend reports them) modifiedMs, permissions, owner, symlinkTarget.
POST /api/v1/remote-fs/connections/{id}/mkdir
Section titled “POST /api/v1/remote-fs/connections/{id}/mkdir”{ "path": "/var/www/new-folder" }{ "ok": true } on success.
POST /api/v1/remote-fs/connections/{id}/rename
Section titled “POST /api/v1/remote-fs/connections/{id}/rename”{ "from": "/var/www/old.txt", "to": "/var/www/new.txt" }POST /api/v1/remote-fs/connections/{id}/delete
Section titled “POST /api/v1/remote-fs/connections/{id}/delete”{ "paths": [ { "path": "/var/www/tmp", "is_dir": true } ] }is_dir defaults to false — needed by backends like S3 that distinguish an object from a prefix.
POST /api/v1/remote-fs/connections/{id}/download
Section titled “POST /api/v1/remote-fs/connections/{id}/download”{ "remote_path": "/var/www/app.log", "local_path": "/Users/me/Downloads/app.log" }202 Accepted with { "transferId": "tr_rest_..." }. Poll GET /api/v1/remote-fs/transfers for progress and completion.
POST /api/v1/remote-fs/connections/{id}/upload
Section titled “POST /api/v1/remote-fs/connections/{id}/upload”{ "local_path": "/Users/me/deploy.zip", "remote_path": "/var/www/deploy.zip" }Same response shape as download.
Every remote-fs route maps backend failures onto a shared {"error":{"code","message"}} body:
| Wire code | HTTP |
|---|---|
auth, permissionDenied | 403 |
notFound | 404 |
alreadyExists, cancelled | 409 |
hostUnreachable, hostKeyRejected | 502 |
unsupported, protocol | 400 |
io (or anything else) | 500 |
API Flows are Dart-owned — the graph lives in <data-dir>/flows/*.json, written only by the Flutter app. The GET routes read those files straight off disk; only the running UI can execute one.
GET /api/v1/flows
Section titled “GET /api/v1/flows”List flow summaries.
{ "flows": [ { "id": "flow-1", "name": "Login + fetch profile", "nodeCount": 4, "edgeCount": 3, "createdAt": 1731081822, "updatedAt": 1731082900 } ]}Corrupt files are skipped silently, mirroring the Dart loader.
GET /api/v1/flows/{id}
Section titled “GET /api/v1/flows/{id}”The full stored flow JSON — nodes, edges, canvas viewport — enriched with a derived subflowIds array: the deduped target ids of every subflow node, in node order, so a client can discover a flow’s sub-flow dependencies without walking node payloads itself. 404 if the id doesn’t exist or the file is corrupt.
POST /api/v1/flows/{id}/run
Section titled “POST /api/v1/flows/{id}/run”Ask the running UI to execute the flow and return its report.
{ "environment": { "token": "abc123" } }environment is optional — per-run variable overrides forwarded verbatim to the Dart executor. The body may be omitted entirely.
| Condition | HTTP |
|---|---|
| flow file missing or corrupt | 404 |
| no sink registered (app not running) | 503 — {"error":{"code":"uiNotRunning",...}} |
| app never answered within the timeout | 504 — {"error":{"code":"timeout",...}} |
| report received | 200 — the run report, forwarded verbatim from the Dart executor |
Runs can take up to two minutes; the route waits out the full timeout before returning 504. See Flows in CI for driving this endpoint outside an interactive AI session.
Scripting
Section titled “Scripting”POST /api/v1/scripts/execute
Section titled “POST /api/v1/scripts/execute”Run an ad-hoc script in test mode. Body wraps the actual input under an input key:
{ "input": { "script": "pro.test('shape', () => pro.expect(pro.response.json().id).toBeDefined());", "variables": {}, "request": { "method": "GET", "url": "https://...", "headers": {}, "body": "" }, "response": { "status": 200, "headers": {}, "body": "{\"id\":1}" }, "phase": "test" }}Returns the engine output — console messages, assertion results, mutated request/response, errors.
GET /api/v1/script-rules
Section titled “GET /api/v1/script-rules”{ "scripting_enabled": true, "rules": [ { "id": "rule-1", "name": "Inject trace header", "method": "GET", "url_pattern": "api.example.com/**", "request_script": "pro.request.headers.upsert('x-trace-id', pro.crypto.uuid());", "response_script": "", "apply_to_request": true, "apply_to_response": false, "enabled": true } ]}POST /api/v1/script-rules
Section titled “POST /api/v1/script-rules”Add a rule. The full ScriptRuleEntry shape is required:
{ "id": "rule-1", "name": "Inject trace header", "method": "GET", "url_pattern": "api.example.com/**", "request_script": "pro.request.headers.upsert('x-trace-id', pro.crypto.uuid());", "response_script": "", "apply_to_request": true, "apply_to_response": false, "enabled": true}| Field | Type | Notes |
|---|---|---|
id | string | Caller-supplied unique id. The MCP layer uses mcp_<ms> for auto-generation; pick anything stable. |
name | string | Display name in the UI. |
method | string | HTTP method (GET, POST, …) or empty if apply_to_request/apply_to_response are method-agnostic. |
url_pattern | string | Glob (*, **). Match is run against the MITM-shaped URL (with :443/:80); the matcher strips defaults so either form works. |
request_script | string | JS source for the request phase. Empty string = no request hook. |
response_script | string | JS source for the response phase. |
apply_to_request | bool | Run request_script for matching requests. |
apply_to_response | bool | Run response_script for matching responses. |
enabled | bool | Per-rule toggle. |
Returns the persisted rule. 400 if the body fails to deserialize.
PUT /api/v1/script-rules/toggle
Section titled “PUT /api/v1/script-rules/toggle”Master switch (separate from per-rule enabled).
{ "enabled": true }PUT /api/v1/script-rules/sync
Section titled “PUT /api/v1/script-rules/sync”Bulk replace the entire rule list — used by Probe’s UI to push state. Body is an array, same element shape as POST /api/v1/script-rules:
[ { "id": "rule-1", "name": "...", "method": "GET", "url_pattern": "...", "request_script": "...", "response_script": "", "apply_to_request": true, "apply_to_response": false, "enabled": true }, { "id": "rule-2", "name": "...", "method": "", "url_pattern": "**", "request_script": "", "response_script": "pro.test('200', () => pro.expect(pro.response.code).equals(200));", "apply_to_request": false, "apply_to_response": true, "enabled": true }]PUT /api/v1/script-rules/{id}
Section titled “PUT /api/v1/script-rules/{id}”Replace one rule. Body is the same ScriptRuleEntry shape as POST. The path id and the body id should match.
DELETE /api/v1/script-rules/{id}
Section titled “DELETE /api/v1/script-rules/{id}”Delete one rule. 204 on success, 404 if id is unknown.
WebSocket
Section titled “WebSocket”Read-only snapshots of WebSocket frames Probe captured through the proxy. There is no connect endpoint — the WebSocket client lives in the app; the proxy only observes frames MITM’d through it. Frames are held in a bounded in-memory store (500 frames / 2 MB per connection, 100 connections), so old connections and frames are evicted over time.
GET /api/v1/websocket/connections
Section titled “GET /api/v1/websocket/connections”List captured WebSocket connections.
{ "items": [ { "connId": 1, "logId": 4213, "url": "wss://api.example.com/ws", "openedTs": 1731081822000, "closed": false, "closedTs": null, "frameCount": 12 } ], "total": 1}logId is the traffic entry id of the upgrade handshake (the GET with Upgrade: websocket), or null when the capture couldn’t be correlated. Use connId with the frames route.
GET /api/v1/websocket/connections/{id}/frames
Section titled “GET /api/v1/websocket/connections/{id}/frames”The captured frames for one connection. 404 if connId is unknown (or already evicted).
{ "connId": 1, "url": "wss://api.example.com/ws", "items": [ { "dir": "out", "opcode": "text", "text": "{\"type\":\"ping\"}", "sizeBytes": 15, "truncated": false, "ts": 1731081822100 } ], "total": 1}dir is in (server → client) or out (client → server). opcode is text / binary / ping / pong / close. For binary frames text is a base64 preview; control frames carry an empty text. Previews are capped at 64 KB — truncated is true when the payload was clipped, and sizeBytes always carries the true payload size.
Status codes
Section titled “Status codes”| Code | Meaning |
|---|---|
| 200 / 202 / 204 | Success (202 is Files transfer routes acknowledging an enqueued download/upload). |
| 400 | Bad request body. Also unsupported/protocol errors from a Files backend. |
| 401 | Missing or wrong bearer token. |
| 403 | Files backend auth failure or permission denied. |
| 404 | Resource (entry id, rule id, flow id, …) doesn’t exist. |
| 409 | Files conflict — entry already exists, or a transfer was already cancelled. |
| 500 | Server-side error — usually a parse failure on the disk-backed compose collections. |
| 502 | Files backend unreachable, or it rejected the host key. |
| 503 | POST /flows/{id}/run only — the Probe app isn’t running to execute the flow. |
| 504 | POST /flows/{id}/run only — the Probe app didn’t answer within the run timeout. |
See also
Section titled “See also”- MCP Server — high-level overview of how
probe-mcpuses this API. - MCP Tool Reference — every tool maps directly to one of the endpoints here.
- File Format — the on-disk shape of saved sessions you can fetch via
/session-tabs/saved/{id}. - Flows in CI — driving
POST /flows/{id}/runoutside an interactive AI session.