Automation and AI
Control API
Cloak Proxy's local, token-protected REST API. Read and replay captured requests, and call any app action through POST /api/rpc from your own scripts.
Cloak Proxy serves a small JSON API on its own port. cloakctl and the MCP server use it, and so can your own scripts: list and read captured requests, replay them, export HAR, and, through one RPC endpoint, do almost anything the app’s windows do. The API answers only programs on this Mac that send the API token.
Where the API is
Section titled “Where the API is”The API lives under /api/ on the proxy’s port. With the default port:
http://127.0.0.1:9090/api/The same paths also work through the proxy at http://cloak.proxy/api/ (and http://cert.cloak/api/ or http://cloakproxy.local/api/), for clients that already use Cloak Proxy as their proxy.
The API is part of the proxy, so it’s available whenever the app is listening, and while cloakctl run is running. Under cloakctl run, every endpoint works except POST /api/rpc, which needs the app.
Authenticate with the token
Section titled “Authenticate with the token”Every request must send the API token in the X-Cloak-Token header. The token is a random 64-character hex string, created the first time anything needs it and stored, readable only by your user account, at:
~/Library/Application Support/CloakProxy/api-token-
Read the token into a shell variable:
Terminal TOKEN="$(cat ~/Library/Application\ Support/CloakProxy/api-token)" -
Send it with each request:
Terminal curl -s -H "X-Cloak-Token: $TOKEN" http://127.0.0.1:9090/api/status{"activeTools" : [ "Map Local" ],"app" : "Cloak Proxy","flowCount" : 128,"port" : 9090,"recording" : true}
Who the API answers
Section titled “Who the API answers”| Check | What happens when it fails |
|---|---|
The connection comes from this Mac (127.0.0.1, ::1 or localhost). |
403 with {"error": "The API is only available on this Mac"}. A phone or another computer using the proxy can’t reach the API, even with Allow other devices to connect on. |
The X-Cloak-Token header matches the token file. |
401 with an error that names the header and the token file’s path. Web pages can’t read the file, so they can’t call the API. |
Endpoints
Section titled “Endpoints”| Method | Path | What it returns |
|---|---|---|
GET |
/api/status |
Port, whether recording is on, request count and active debugging tools. |
GET |
/api/flows |
Summaries of captured requests, newest last. Takes the filters below. |
GET |
/api/flows/{id} |
One request in full: headers, bodies and WebSocket messages. |
GET |
/api/flows/{id}/curl |
The request as a cURL command, as text/plain. |
GET |
/api/flows/{id}/har |
The request as a one-entry HAR file. |
POST |
/api/flows/{id}/replay |
Sends the request again, unchanged, and returns the new request in full. |
GET |
/api/export/har |
Every request the proxy remembers, as HAR. |
POST |
/api/rpc |
Runs an app action. See Call app actions. |
All responses are JSON (application/json) except /curl, and carry Cache-Control: no-store.
Which requests the API sees
Section titled “Which requests the API sees”The API reads the proxy engine’s own list of the most recent 5,000 requests, not the app’s traffic list. When the list passes 5,000, the oldest 1,000 are dropped. The list:
- starts empty each time the proxy starts or restarts (for example after you change the port),
- isn’t emptied by Clear Session in the app,
- doesn’t include sessions you open from files,
- only grows while recording is on.
List requests
Section titled “List requests”GET /api/flows accepts these query parameters. All are optional and can be combined.
| Parameter | Example | Keeps requests where |
|---|---|---|
search |
search=checkout |
The URL or the app name contains the text (not case-sensitive). |
host |
host=api.example.com |
The host contains the text. |
status |
status=404 |
The response status equals the number. |
app |
app=Safari |
The app name contains the text. |
method |
method=POST |
The HTTP method matches. |
since |
since=120 |
The id is greater than this. Use the last id you saw to poll for new requests. |
errors |
errors=1 |
The request failed or the status is 400 or higher. |
limit |
limit=200 |
Returns at most this many, the newest ones. Default 50. |
curl -s -H "X-Cloak-Token: $TOKEN" \ "http://127.0.0.1:9090/api/flows?host=api.example.com&errors=1&limit=10"Each summary has these fields:
| Field | Type | Meaning |
|---|---|---|
id |
Number | The request’s id, as in the traffic list’s # column. |
method, url, host |
String | The request line. |
startedAt |
String | ISO 8601 time the request started. |
state |
String | pending, completed, tunneled (HTTPS passed through without decrypting) or failed. |
error |
String | The failure message, when state is failed. |
decrypted |
Boolean | Whether HTTPS was decrypted. |
app |
String or null | The app that sent it. |
tags |
Array of strings | Debugging tools that touched it, such as Map Local. |
status, responseSize, contentType |
Number, number, string | Present when there’s a response. |
durationMs |
Number | Present once the request finished. |
Get one request
Section titled “Get one request”GET /api/flows/{id} adds request and response objects to the summary. Each has headers (a list of {name, value}) and body; the response also has status and reason. Bodies are decompressed text, cut at 200,000 bytes with a note saying how many bytes were left out. Binary bodies come as {"base64": "…", "size": N}. WebSocket connections add webSocketMessages: the last 200 messages with direction, kind and text.
An unknown id returns 404 with {"error": "No request with id 12"}.
Call app actions with POST /api/rpc
Section titled “Call app actions with POST /api/rpc”POST /api/rpc runs an action in the running app, through the same code as the app’s buttons, so its windows update at once. Send a JSON body with the method name and its parameters:
curl -s -H "X-Cloak-Token: $TOKEN" -H "Content-Type: application/json" \ -d '{"method": "ssl_proxying", "params": {"action": "add_host", "value": "*.example.com"}}' \ http://127.0.0.1:9090/api/rpcThe answer wraps the method’s result:
{ "result" : { "apps" : [ ], "hosts" : [ { "enabled" : true, "pattern" : "*.example.com" } ] }}| Status | When |
|---|---|
200 |
The method ran. Its output is in result. |
400 |
The body isn’t {"method": "…", "params": {…}}. |
422 |
The method failed, for example a missing parameter or an unknown id. The reason is in error. |
501 |
The proxy is cloakctl run, which has no app to run actions in. |
Capture
Section titled “Capture”| Method | Parameters | What it does |
|---|---|---|
status |
None | Port, whether it’s listening and recording, whether the macOS proxy is on, whether the certificate is trusted, request count, paused breakpoints, active tools, SSL proxying hosts and apps, whether other devices may connect, captured domains, and apps bypassing the proxy. |
set_recording |
on (boolean) |
Pauses or resumes recording. The proxy keeps forwarding either way. |
clear_session |
None | Removes every request from the traffic list. Returns how many were cleared. |
set_macos_proxy |
on (boolean) |
Turns capturing of macOS traffic on or off. Your previous proxy settings come back when it’s off. |
list_apps |
None | Apps in the traffic, with request counts and whether SSL proxying is on for them. |
list_domains |
None | Hosts in the traffic, with request counts and whether they’re decrypted. |
Requests
Section titled “Requests”| Method | Parameters | What it does |
|---|---|---|
annotate_request |
id; optional color (none, red, orange, yellow, green, blue, purple, gray) and comment |
Highlights a request and sets its comment. An empty comment clears it. |
edit_and_replay |
Optional id, method, url, headers, body |
Sends a modified copy of a captured request, or a new request when there’s no id (then url is required). headers as an object ({"Name": "value"}) is merged over the original, and null removes a header; as a list of {name, value} it replaces them all. Returns the new request in full. |
compare_requests |
a, b (request ids) |
A line diff of the two requests and their responses. |
export_session |
format (session, har, csv, postman or openAPI), path; optional ids (list of numbers), redact (boolean) |
Writes requests to a file. Without ids, every request in the traffic list. |
import_session |
path |
Opens a HAR, Charles .chlsj or Cloak Proxy session file into the traffic list. |
SSL proxying, settings and certificate
Section titled “SSL proxying, settings and certificate”| Method | Parameters | What it does |
|---|---|---|
ssl_proxying |
action (list, add_host, remove_host, add_app, remove_app); value for every action except list |
Lists or changes which hosts and apps are decrypted. Host patterns accept wildcards. Returns the current hosts and apps. |
get_settings |
None | The proxy settings as JSON, plus autoEnableMacOSProxy. An external proxy password shows as ••••. |
update_settings |
changes (object of setting names and values) |
Changes settings. See the keys below. |
certificate |
None | The root certificate’s name, file path, whether macOS trusts it, and its PEM text. |
update_settings accepts these keys: port, listenOnAllInterfaces, http2ToClients, maxCapturedBodyBytes, bypassHosts, allowedClientIPs, insecureUpstreamHosts, externalProxy, socks, reverseProxies, tlsKeyLogPath, sslProxyingHosts, sslProxyingApps and autoEnableMacOSProxy. Objects such as externalProxy and socks merge into the current value; lists replace the whole list, so start from get_settings. Changing port, listenOnAllInterfaces, socks or reverseProxies restarts the proxy. The result lists the keys applied, whether the proxy restarted, and the port.
{ "method": "update_settings", "params": { "changes": { "externalProxy": { "isEnabled": true, "host": "proxy.example.com", "port": 8080 } } } }Trusting or removing the certificate needs your Mac password, so it isn’t available through the API. Use Settings › Certificate.
Debugging tools
Section titled “Debugging tools”Tool names are breakpoint, mapLocal, mapRemote, scripting, blockList, allowList, networkConditions, dnsSpoofing, noCaching, mockSets and alerts. The app also accepts their titles, such as Map Local.
| Method | Parameters | What it does |
|---|---|---|
list_rules |
Optional tool |
Each tool’s title, whether it’s on, a summary, its rules, and an exampleRule showing every field a rule has. |
upsert_rule |
tool, rule (object) |
Creates a rule (no id) or changes one (with its id; only the given fields change). Missing fields take the example rule’s values. Turns the tool on. Not for noCaching, which has no rules. |
delete_rule |
tool, id |
Deletes a rule. |
set_tool_enabled |
tool, enabled (boolean) |
Turns a tool on or off. Its rules are kept. |
rule_from_request |
tool, id (request id); optional open (boolean) |
Makes a rule matching a captured request, like right-click › Tools in the traffic list. With open, also shows it in the Tools window. |
script_logs |
Optional limit (default 100) |
Recent console output from Scripting rules: time, level, script and message. |
See Rules and matching for what rule fields mean.
Breakpoints
Section titled “Breakpoints”| Method | Parameters | What it does |
|---|---|---|
list_paused |
None | Requests and responses held by Breakpoint rules, each with its paused-message id, request id, stage, rule name, and the request (and response). |
resolve_paused |
id; optional action (send, send_original, abort; default send), request ({method, url, headers, body}), response ({status, headers, body}) |
Applies the edits and sends the message on, sends the original unedited, or aborts it. |
Insights
Section titled “Insights”| Method | Parameters | What it does |
|---|---|---|
insights |
Optional section (summary, endpoints, security, secrets, performance, schema, markdown; default summary), limit (default 50) |
The Insights analysis of the traffic list. markdown returns the full report. Secret values are masked. |
Apps that ignore the proxy
Section titled “Apps that ignore the proxy”| Method | Parameters | What it does |
|---|---|---|
bypassing_apps |
None | Apps connecting straight to the internet, with their direct connections, and apps already captured through DNS with their domains. |
suggest_capture_domains |
app |
The domains to capture for that app: recommended and others. |
open_capture |
app |
Opens the capture sheet in the app. You confirm it there, because capturing needs your Mac password. |
See Apps that ignore the proxy.
Windows
Section titled “Windows”| Method | Parameters | What it does |
|---|---|---|
open |
target: tools (optional tool), insights, api_client, mcp, breakpoints, setup (optional page: mac, ios, simulator, android, other), settings (optional page: general, ssl, bypass, certificate, network, advanced), or request (with id) |
Brings Cloak Proxy forward and shows that window, page or request. |
Use the API from code
Section titled “Use the API from code”import json, pathlib, urllib.request
TOKEN = (pathlib.Path.home() / "Library/Application Support/CloakProxy/api-token").read_text().strip()BASE = "http://127.0.0.1:9090/api/"# Talk to Cloak Proxy directly, even if HTTP_PROXY is set.opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
def call(path, body=None): data = json.dumps(body).encode() if body is not None else None request = urllib.request.Request(BASE + path, data=data, headers={"X-Cloak-Token": TOKEN, "Content-Type": "application/json"}) with opener.open(request) as response: return json.load(response)
for flow in call("flows?errors=1&limit=20"): print(flow["id"], flow.get("status"), flow["url"])
call("rpc", {"method": "set_recording", "params": {"on": False}})import { readFileSync } from "node:fs";import { homedir } from "node:os";
const token = readFileSync(`${homedir()}/Library/Application Support/CloakProxy/api-token`, "utf8").trim();const base = "http://127.0.0.1:9090/api/";
async function call(path, body) { const response = await fetch(base + path, { method: body ? "POST" : "GET", headers: { "X-Cloak-Token": token, "Content-Type": "application/json" }, body: body ? JSON.stringify(body) : undefined, }); if (!response.ok) throw new Error((await response.json()).error); return response.json();}
const failures = await call("flows?errors=1&limit=20");for (const flow of failures) console.log(flow.id, flow.status, flow.url);
await call("rpc", { method: "set_tool_enabled", params: { tool: "mapLocal", enabled: false } });TOKEN="$(cat ~/Library/Application\ Support/CloakProxy/api-token)"API=http://127.0.0.1:9090/api
# New requests since id 120curl -s -H "X-Cloak-Token: $TOKEN" "$API/flows?since=120"
# Replay request 12curl -s -X POST -H "X-Cloak-Token: $TOKEN" "$API/flows/12/replay"
# Turn on macOS capturecurl -s -H "X-Cloak-Token: $TOKEN" -H "Content-Type: application/json" \ -d '{"method": "set_macos_proxy", "params": {"on": true}}' "$API/rpc"Replace 9090 if you changed the port in Settings › General.