REST API Reference

The API is available at http://127.0.0.1:8081/api when the proxy is running. All endpoints are served from the same port as the web UI.

GET /api/requests

Query captured requests. Returns paginated results.

Query parameters match the CLI requests command:

ParameterDescription
hostFilter by hostname (substring match)
statusFilter by HTTP status code
methodFilter by HTTP method
content_typeFilter by content type
client_protocolFilter by client-hop wire protocol: http/1.1 or h2. 400 on an unrecognised value
origin_protocolFilter by origin-hop wire protocol: http/1.1 or h2. Same rules, combinable with client_protocol
searchSearch URLs (substring match)
sinceAfter this time (Unix ms or ISO date)
untilBefore this time (Unix ms or ISO date)
limitMaximum number of results (default: 100)
offsetPagination offset
# All requests
curl http://127.0.0.1:8081/api/requests

# Filtered
curl "http://127.0.0.1:8081/api/requests?host=example.com&status=200&limit=50"

# The mixed-hops case: h2 client, HTTP/1.1 origin
curl "http://127.0.0.1:8081/api/requests?client_protocol=h2&origin_protocol=http%2F1.1"

Response:

{
  "data": [
    {
      "id": "...",
      "timestamp": 1700000000000,
      "method": "GET",
      "client_protocol": "h2",
      "origin_protocol": "http/1.1",
      ...
    }
  ],
  "total": 142,
  "limit": 100,
  "offset": 0
}

GET /api/requests/:id

Get full details for a single request, including headers and base64-encoded bodies.

curl http://127.0.0.1:8081/api/requests/a1b2c3d4-e5f6-7890-abcd-ef1234567890

GET /api/requests/:id/messages

Get captured WebSocket frames for a connection, paginated. :id is the id of the request whose kind is websocket (the recorded 101 handshake).

curl 'http://127.0.0.1:8081/api/requests/a1b2c3d4-e5f6-7890-abcd-ef1234567890/messages?limit=100&offset=0'

Response:

{
  "data": [
    { "direction": "sent", "opcode": "text", "payload": "...", "timestamp": 1700000000000 }
  ],
  "total": 812,
  "limit": 500,
  "offset": 0
}

limit defaults to 500, offset to 0; both must be non-negative integers. An unknown or not-yet-populated id returns an empty page (total: 0), not a 404. Payloads are base64-encoded unconditionally, regardless of opcode -- this is intentionally different from the CLI's --format json/--format agent output, which decodes text frames to UTF-8. Check which surface you're reading from before assuming either encoding.

DELETE /api/requests

Delete all captured traffic.

curl -X DELETE http://127.0.0.1:8081/api/requests

GET /api/status

Get proxy status.

curl http://127.0.0.1:8081/api/status

Response:

{
  "running": true,
  "proxyPort": 8080,
  "requestCount": 142,
  "dbSizeBytes": 3358720
}

GET /api/throttle

Get current bandwidth throttle settings and the available presets.

curl http://127.0.0.1:8081/api/throttle

Response:

{
  "settings": { "enabled": true, "downKbps": 780, "upKbps": 330, "latencyMs": 100 },
  "presets": { "56k": { "...": "..." } }
}

PUT /api/throttle

Apply a preset, or set explicit down/up/latency values.

curl -X PUT http://127.0.0.1:8081/api/throttle -H "Content-Type: application/json" \
  -d '{"preset":"3g"}'

{"preset":"off"} disables throttling. A preset takes full precedence over any enabled/downKbps/upKbps/latencyMs fields sent alongside it -- it replaces the settings object rather than merging into it. Or send explicit fields (omitted fields fall back to the current setting):

curl -X PUT http://127.0.0.1:8081/api/throttle -H "Content-Type: application/json" \
  -d '{"enabled":true,"downKbps":500,"upKbps":100,"latencyMs":200}'

Settings are persisted to ~/.laurel-proxy/config.json before being applied to the live throttler. If the write fails, the endpoint returns 500 and the change is not applied. A successful request returns { "settings": { ... } }.

GET /api/ca.crt

Download the Laurel Proxy CA certificate. Useful for installing on mobile devices -- open this URL in the device's browser to trigger a certificate install prompt.

curl -O http://127.0.0.1:8081/api/ca.crt

POST /api/proxy/start

Start the proxy server.

curl -X POST http://127.0.0.1:8081/api/proxy/start

POST /api/proxy/stop

Stop the proxy server. The API remains available after stopping the proxy.

curl -X POST http://127.0.0.1:8081/api/proxy/stop

GET /api/system-proxy

Report whether the macOS system proxy is currently pointed at Laurel Proxy. Returns a JSON object with a single boolean enabled field.

curl http://127.0.0.1:8081/api/system-proxy

POST /api/system-proxy/enable

Route all macOS HTTP and HTTPS traffic through Laurel Proxy. This is the programmatic equivalent of laurel-proxy proxy-on. macOS only.

curl -X POST http://127.0.0.1:8081/api/system-proxy/enable

POST /api/system-proxy/disable

Restore the previous system proxy settings, equivalent tolaurel-proxy proxy-off. macOS only.

curl -X POST http://127.0.0.1:8081/api/system-proxy/disable

POST /api/replay

Replay a previously captured request.

curl -X POST http://127.0.0.1:8081/api/replay \
  -H "Content-Type: application/json" \
  -d '{"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}'

POST /api/websocket/replay

Reopen a WebSocket connection and resend the client-sent data frames from a previously recorded connection, preserving the original inter-frame gaps. Only frames with opcode text or binary and direction: 'sent' are replayed -- control frames (ping/pong/close) are managed by the WebSocket client itself.

curl -X POST http://127.0.0.1:8081/api/websocket/replay -H "Content-Type: application/json" \
  -d '{"requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"}'

Prefer requestId over the alternative {url, frames} body shape: a recorded connection's base64-encoded frames routinely exceed Express's default 100 kb body-size limit, so posting them inline fails before reaching the replay logic. With requestId the server reads the frames from storage itself, keeping the HTTP request body tiny.

A replay refuses (400) if the connection has any truncated client-sent data frame recorded, since replaying a payload cut off at maxBodySize would send corrupted data to a real server. The response includes a stoppedBecause field (close, idle, timeout, or error) plus frameCount and sentAll -- stoppedBecause alone is not a success signal. idle fires whenever the server's first reply takes longer than 500ms, which is routine for real work happening server-side, and close can mean a clean end or a connection cut short after one frame of three. Check sentAll to know whether the replay actually finished what it was asked to send.

GET /api/events

Server-Sent Events stream for real-time updates.

curl -N http://127.0.0.1:8081/api/events

Events are named:

  • event: request -- new captured request (data is the request record as JSON)
  • event: status -- proxy state change (data includes running state and proxy port)
  • event: ws-message -- a new WebSocket frame, pushed immediately as it's captured (not batched like request), in the same base64-encoded shape as GET /api/requests/:id/messages

POST /api/shutdown

Shut down the entire process (proxy + API + web UI).

curl -X POST http://127.0.0.1:8081/api/shutdown