Local HTTP API
The MCP server runs on this API, and you can call it directly too — from Python, Node, n8n, Postman or anything that speaks HTTP.
Address and token
Section titled “Address and token”After Automation → Enable API / MCP access, Accovod starts a server on 127.0.0.1 and writes its details to a file:
~/Library/Application Support/Accovod/control.json{ "app": "Accovod", "platform": "macos", "apiVersion": "1.0", "port": 25252, "token": "8f3c…", "baseUrl": "http://127.0.0.1:25252"}- The port is 25252 when it’s free. Reading
baseUrlfrom the file is more reliable than hard-coding the address. - The file is deleted when Accovod quits. No file means the app isn’t running or the API is off.
- Rotate the token with Automation → Regenerate token.
Calling it
Section titled “Calling it”There’s one endpoint — POST /v1/rpc — with the method and parameters in the body:
CONTROL=~/Library/Application\ Support/Accovod/control.jsonBASE=$(jq -r .baseUrl "$CONTROL")TOKEN=$(jq -r .token "$CONTROL")
curl -s "$BASE/v1/rpc" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"method":"profiles.list","params":{}}'import json, pathlib, urllib.request
control = json.loads(pathlib.Path( "~/Library/Application Support/Accovod/control.json").expanduser().read_text())
def call(method, **params): req = urllib.request.Request( control["baseUrl"] + "/v1/rpc", data=json.dumps({"method": method, "params": params}).encode(), headers={"Authorization": f"Bearer {control['token']}", "Content-Type": "application/json"}) reply = json.load(urllib.request.urlopen(req)) if not reply["ok"]: raise RuntimeError(reply["error"]) return reply["result"]
for p in call("profiles.list")["profiles"]: print(p["name"], p["url"])GET /v1/health answers without a token — use it to check whether the app is running.
Response format
Section titled “Response format”Every response uses the same envelope:
{ "ok": true, "result": { … } }{ "ok": false, "error": { "code": "PROFILE_NOT_FOUND", "message": "…", "hint": "…" } }hint says what to fix in the request. An optional id in the request is echoed back.
Key rules
Section titled “Key rules”profileIdis the only reliable profile identifier. Take it fromprofiles.listand pass it to everypage.*method.indexchanges when the list is reordered.- Start with
app.status. It reports the profile mode (alloractiveOnly), the active group and whether the agent is stopped. - Elements come from
page.snapshot. It returns interactive elements with a ready-madetargetobject. Pass it topage.clickandpage.fillas is. Take a new snapshot after navigating. - Retries are safe.
profiles.create,profiles.createManyandprofiles.importManyrequire anidempotencyKey: a retry with the same key returns what was already created instead of a duplicate. - Deletion needs
confirm: true. Without it,profiles.deleteandsessions.deletedelete nothing and report what would have been deleted. - Long work can run through
operations.start, with the result fetched byoperations.get— useful when your client times out long calls.
Common errors
Section titled “Common errors”| Code | HTTP | Meaning |
|---|---|---|
UNAUTHORIZED |
401 | Missing or wrong token. |
BROWSER_ORIGIN_BLOCKED |
403 | The request came from a page in a browser — those are refused. |
AGENT_STOPPED |
409 | The person pressed Stop. Everything except app.status is refused until Resume. |
PROFILE_NOT_FOUND |
404 | No such profileId in the open group. |
PROFILE_NOT_LIVE |
409 | The profile has no open tab — in activeOnly mode call profiles.open first. |
REVISION_MISMATCH |
409 | The profile changed since you read it. Nothing was written — read it again and retry. |
PROFILE_LIMIT_REACHED |
409 | The cloud group has hit the plan’s limit. |
TARGET_NOT_FOUND / AMBIGUOUS_TARGET |
404 / 409 | No element, or several, matched — nothing was clicked. |
ELEMENT_OCCLUDED |
409 | A banner or dialog covers the element — the message names it. |
CONFIRMATION_REQUIRED |
400 | Deletion without confirm: true. |
Every method and its main parameters are in the tools reference. MCP tool and API method names map simply: profiles_list ↔ profiles.list, page_wait_for ↔ page.waitFor.
