HTTP API
The daemon serves this API, so both relay-core start and a foreground relay-core run expose it. Default base URL: http://127.0.0.1:8082/api/v1/; when that port is taken the daemon binds an ephemeral one and publishes the real port in $RELAY_DATA_DIR/daemon.json (relay-core status prints it).
The daemon generates a bearer token and requires Authorization: Bearer <T>; the token lives in the same 0600 manifest. The Web UI is the exception: a browser cannot attach headers to the EventSource that carries the flow stream, so the URL the daemon prints has the token in the fragment — which a browser never sends to a server — and the page trades it for a SameSite=Strict same-origin cookie, clearing the address bar. The middleware accepts the bearer header or that cookie. CORS is emitted only when --api-cors is set; otherwise the server is strict.
Proxy lifecycle
These endpoints are what "start the proxy" and "stop the proxy" look like on the wire: the CLI (start / stop) and the MCP tools (proxy_start / proxy_stop) all go through them.
GET /api/v1/proxy/status
{"phase": "running", "port": 8080, "started_at_ms": 1789817862851, "last_error": null} POST /api/v1/proxy/start
Start the proxy. Idempotent: an already-running proxy answers 200 with already_running, a start this call performed answers 201, and a failure such as a taken port answers 409 with a stable code (start_failed) instead of success for a proxy that never bound.
{"port": 8080, "transparent": false} POST /api/v1/proxy/stop
Stop the proxy; the daemon and its history stay. Idempotent: already_stopped when nothing was running.
POST /api/v1/daemon/shutdown
Stop the proxy and exit the daemon. Once accepted, the daemon clears its registry, stops the proxy and releases the data-directory lock.
An X-Relay-Client header may carry a client's own label (for example cli:relay-core start) which is recorded in the audit trail. It is never used for authorization; it exists so "who made this change" is answerable after the caller exits. Read those records with GET /api/v1/audit?kind=proxy_lifecycle_changed.
Version & Status
GET /api/v1/version
Engine version, API version, and a list of registered capability names.
{
"engine_version": "0.14.0",
"api_version": "1",
"capabilities": ["flows", "rules", "intercepts", "mock", "metrics", "status", "events", "scripts"]
} GET /api/v1/status
Runtime lifecycle snapshot — uptime, current rules count, intercepts pending, etc.
Flows
GET /api/v1/flows
Search and paginate captured flows. All parameters are optional.
Query Parameters:
host,path,path_contains,method— substring matchesstatus_min,status_max— status code rangehas_error— only flows with errorsis_websocket— only WebSocket flowslimit— page size, default 50, clamped to 1–200offset— pagination offset
{
"items": [{ "id": "fld_...", "method": "GET", "path": "/users", "status": 200 }],
"returned": 10,
"limit": 50,
"offset": 0
} Redaction is applied to URLs (sensitive query keys) and headers before the response leaves the server, so secrets never appear in the result even if they were observed in traffic.
GET /api/v1/flows/:id
Full Flow JSON including headers and bodies. The same redaction rules apply.
POST /api/v1/flows/:id/replay
Re-send a captured HTTP request as stored. This call cannot edit headers or body. The replay enters through the running proxy, so it is captured as a new flow and the current rules and scripts apply. The call returns 503 when the proxy is not running. ?accept_invalid_certs=true skips TLS verification on the replay client (debug only); the engine CA is trusted automatically. WebSocket flows return 400.
GET /api/v1/flows/:id/har
Export a single flow as a HAR 1.2 entry.
GET /api/v1/flows/export/har
Batch HAR export. Accepts the same query parameters as GET /api/v1/flows.
Rules
GET /api/v1/rules
List all interception rules. Each entry is a full Rule (id, name, active, stage, priority, termination, filter, actions). The enable flag is active, not enabled.
PUT /api/v1/rules
Create or replace a rule by id.
{
"id": "rul_01HX5K9P3Q8Y2Z7",
"name": "Tag API requests",
"active": true,
"stage": "RequestHeaders",
"priority": 100,
"termination": "Continue",
"filter": {
"type": "And",
"config": [
{ "type": "Method", "config": { "mode": "Exact", "value": "GET" } },
{ "type": "Host", "config": { "mode": "Glob", "value": "*.example.com" } },
{ "type": "Path", "config": { "mode": "Glob", "value": "/api/*" } }
]
},
"actions": [{ "type": "Tag", "config": { "key": "kind", "value": "api" } }]
} DELETE /api/v1/rules/:id
Delete a rule.
POST /api/v1/mock
Quickly create a MockResponse rule. Body fields: url_pattern, status, body, content_type.
Intercepts
POST /api/v1/intercepts
Create a one-shot intercept breakpoint.
{
"url_pattern": "api.example.com",
"phase": "request"
} The response includes the generated rule_id used to refer to the intercept.
GET /api/v1/intercepts
List pending intercepts (both HTTP and WebSocket).
POST /api/v1/intercepts/:key/resume
Resume an intercepted flow. The body accepts action (continue or drop) and FlowModification fields: method, url, request_headers, request_header_upserts, request_body, status_code, response_headers, response_header_upserts, response_body, message_content. *_header_upserts add or overwrite by name and keep the rest; request_headers / response_headers replace the whole map.
Policy & Redaction
GET /api/v1/policy
Return the current ProxyPolicy, including the redaction block and (if configured) the upstream block. The password field of an upstream auth is masked as ***.
PATCH /api/v1/policy
Apply a partial update. The only top-level keys are redaction, upstream, and retention; unknown fields return 400. All three are optional:
{
"redaction": {
"enabled": true,
"redact_bodies": true,
"sensitive_header_names": ["authorization", "x-api-key", "cookie"],
"sensitive_query_keys": ["token", "api_key", "password"]
},
"upstream": {
"proxy_url": "http://corp-proxy.internal:8080",
"bypass_hosts": ["*.internal", "127.0.0.1"],
"fail_open": false
}
} Redaction is enabled by default. A default policy masks the sensitive header and
query names below, so a fresh install does not persist or serve credentials verbatim — including to
anything reading the API. Bodies are the exception: redact_bodies stays off, because
masking payload content would change what rules and scripts see. Turn redaction off explicitly if you
need raw headers.
Default sensitive header names: authorization, proxy-authorization, cookie, set-cookie, x-api-key, x-auth-token.
Default sensitive query keys: token, access_token, refresh_token, api_key, apikey, password, secret.
Redaction is applied to the HTTP request/response, the WebSocket handshake, every WebSocket message, flow summaries, search results, and the SSE event stream. A new proxy_url, auth, fail_open, or bypass_hosts applies to later requests, and a host matching bypass_hosts is dialled directly. If the new proxy URL cannot be resolved and fail_open is false, the previous connector stays in use.
Scripting
POST /api/v1/script
Hot-load a script into the running engine.
{ "script": "globalThis.onRequestHeaders = (ctx, flow) => { console.log(flow.layer.data.request.url); return flow; }" } Events
GET /api/v1/events
Server-Sent Events stream of live updates.
Event types:
flow— new or updated flow (full Flow JSON in the data field)ws-message— new WebSocket messagehttp-body— a body kept within budget; data is{ flow_id, direction, body }body-budget-exceeded— one direction exceeded the body budget; data is{ flow_id, direction }response-trailers— HTTP/2 trailers; data is{ flow_id, trailers }flow-event— typed events (intercept pause/resume, rule mutations, terminal close) alongsideflowsnapshotsaudit— control-plane mutation (rule change, intercept resolved, script reloaded, policy updated)audit-lagged— audit channel dropped events, server resyncslagged— flow / events channel dropped events, TUI resyncslifecycle— proxy start / stop / reload
Metrics
GET /api/v1/metrics
JSON metrics snapshot. Same fields as relay-core metrics (see CLI reference).
GET /api/v1/metrics/prometheus
Prometheus text format metrics.
# HELP relay_core_flows_total Total number of flows captured
# TYPE relay_core_flows_total counter
relay_core_flows_total 12847
# HELP relay_core_request_duration_ms Request duration in milliseconds
# TYPE relay_core_request_duration_ms histogram
relay_core_request_duration_ms_bucket{le="1"} 10023
relay_core_request_duration_ms_bucket{le="5"} 12500
relay_core_request_duration_ms_bucket{le="10"} 12800
relay_core_request_duration_ms_bucket{le="+Inf"} 12847 Script-engine metrics are emitted per hook: relay_core_script_hook_duration_us{hook}, relay_core_script_hook_invocations_total{hook}, relay_core_script_hook_errors_total{hook}, plus relay_core_script_env_access_total, relay_core_script_fetch_total, and relay_core_script_fetch_rejected_total.
Audit
GET /api/v1/audit
Query the control-plane mutation audit log.
Query Parameters:
since_ms,until_ms— epoch milliseconds; either may be omittedactor—runtime,http,tauri,probe, orclikind—rule_changed,intercept_resolved,script_reloaded,policy_updatedoutcome—successorfailure
The response is a JSON array of AuditEvent entries. Each event records who made the change, when, and the relevant before/after diff for mutations.