Skip to content

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.

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 0600 on 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.

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:

Terminal window
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, ...}

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
}

List captured entries with optional filters.

Query paramTypeDescription
limitintDefault 50, capped at 500.
offsetintDefault 0.
hoststringSubstring match against host.
methodstringExact match.
statusintExact status code (sets both min and max).
status_min / status_maxintStatus range.
pathstringURL path filter.
searchstringSubstring search across URL and body.
has_errorbooltrue for status ≥ 400 only.
since_idintOnly entries with id > since_id.
sinceintOnly entries with timestamp >= since (ms since epoch).
Terminal window
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
}

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.

Drop every entry. Returns 204 No Content.

Drop a single entry. 204 on success, 404 if missing.

Plain text — a runnable cURL command. Content-Type: text/plain.

Plain text — raw HTTP request and response.

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.

Compare two entries. Returns a structured diff over method, URL, status, headers, and body.

{ "capturing": true, "entry_count": 142 }

Enable recording. Returns 204.

Disable recording — traffic still proxies, it just isn’t stored. Returns 204.

Array of {pattern, intercept_request, intercept_response}.

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.

Delete a single breakpoint by pattern.

{ "pattern": "api.example.com/users/*" }

Drop every breakpoint. 204.

A breakpoint or intercept-host match parks the request until you resume it.

Array of {id} — one per paused request.

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.

Array of host strings.

Add a host. 204.

Remove a host. 204.

Clear the list. 204.

Same shape as ignored hosts, but the key is a client label — process name (loopback) or IP (remote phone).

  • GET /api/v1/ignored-clients
  • PUT /api/v1/ignored-clients/{label}
  • DELETE /api/v1/ignored-clients/{label}

The label segment is URL-encoded; the server decodes it.

Hosts that pause matching requests for inspection (a coarser form of breakpoint).

  • GET /api/v1/intercept-hosts
  • PUT /api/v1/intercept-hosts/{host}
  • DELETE /api/v1/intercept-hosts/{host}

Current global throttle state.

Set the global bandwidth cap.

{ "bytes_per_second": 102400 }

Disable the global throttle.

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.

Active session tabs in the running app.

Entries inside a specific tab.

Sessions persisted to disk under ~/.probe/sessions/.

Entries inside a saved session.

Array of {id, name, request_count, folder_count, environment_count, created_at}.

Full collection JSON — folders, requests, environments, default headers, default auth.

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 }
]
}
FieldTypeNotes
keystringVariable name; referenced as {{key}} in URL/headers/body.
defaultValuestringPersisted value.
currentValuestring?Session-only override. Cleared on app restart.
enabledboolWhen false, the variable behaves as if undefined.
isSecretboolMasks the value in console output and the variable hover preview.

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).

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": "..." } }
{ "enabled": true }

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
}
FieldTypeNotes
idstringCaller-supplied; pick any stable value.
url_patternstringGlob (*, **). Default :443/:80 is stripped before matching.
methodstringMethod to match. Ignored when match_all_methods is true.
match_all_methodsboolWhen true, method is ignored. Default false.
enabledboolPer-rule toggle. Default true.
sourcestring"inline_body" or "local_file". Anything else falls back to inline.
file_pathstring?Required when source = "local_file". Absolute path on the host running Probe.
inline_bodystring?Used when source = "inline_body". Raw bytes (UTF-8 or base64-encoded for binary).
inline_formatstring?MIME hint (application/json, text/html, image/png, …). Used to set Content-Type on the served response.
status_codeintHTTP status to return. Default 200.
response_headersobjectExtra response headers.
delay_msintArtificial delay before responding. Default 0.

Returns the persisted rule.

Replace a rule. Body shape identical to POST.

Delete by id. 204.

Flip the rule’s enabled flag. Returns the updated rule.

Same endpoint shape as Map Local under /api/v1/map-remote. The rule body differs — it splits source-match criteria from destination-rewrite fields.

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.

{ "enabled": true }

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
}
FieldTypeNotes
idstringCaller-supplied.
enabledboolDefault true.
source_schemestringhttp or https.
source_hoststringHost to match (no port).
source_portint?null means scheme default (443/80).
source_pathstringPath glob. * captures one segment substring (regex .*, anchored). Captures are addressable in dest_path as $1, $2, … in order.
methodstringMethod to match. Ignored when match_all_methods is true.
match_all_methodsboolDefault false.
dest_schemestringhttp or https.
dest_hoststringRewrite target host.
dest_portint?null means scheme default.
dest_pathstringRewritten path. Use $1, $2, … to substitute captures from source_path.
preserve_original_host_headerboolWhen true, the rewritten request keeps the original Host header — useful for API gateways that route by Host. Default false.

Returns the persisted rule.

Replace a rule. Body shape identical to POST.

Delete by id. 204.

Flip enabled. Returns the updated rule.

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.

{
"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
}
]
}

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
}
FieldTypeNotes
idstringCaller-supplied unique id. The MCP layer uses mcp_<ms> for auto-generation; pick anything stable.
namestringDisplay name in the UI.
methodstringHTTP method (GET, POST, …) or empty if apply_to_request/apply_to_response are method-agnostic.
url_patternstringGlob (*, **). Match is run against the MITM-shaped URL (with :443/:80); the matcher strips defaults so either form works.
request_scriptstringJS source for the request phase. Empty string = no request hook.
response_scriptstringJS source for the response phase.
apply_to_requestboolRun request_script for matching requests.
apply_to_responseboolRun response_script for matching responses.
enabledboolPer-rule toggle.

Returns the persisted rule. 400 if the body fails to deserialize.

Master switch (separate from per-rule enabled).

{ "enabled": true }

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 }
]

Replace one rule. Body is the same ScriptRuleEntry shape as POST. The path id and the body id should match.

Delete one rule. 204 on success, 404 if id is unknown.

CodeMeaning
200 / 204Success.
400Bad request body.
401Missing or wrong bearer token.
404Resource (entry id, rule id, …) doesn’t exist.
500Server-side error — usually a parse failure on the disk-backed compose collections.
  • MCP Server — high-level overview of how probe-mcp uses 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}.