Skip to content

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.

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.

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
  1. Read the token into a shell variable:

    Terminal
    TOKEN="$(cat ~/Library/Application\ Support/CloakProxy/api-token)"
  2. 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
    }
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.
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.

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.

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.
Terminal
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 /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"}.

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:

Terminal
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/rpc

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

Request body
{ "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.

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.

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

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.
cloak_api.py
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}})

Replace 9090 if you changed the port in Settings › General.