AI agents & API
Local HTTP API
The Accovod local HTTP API on macOS and Windows — address and token from control.json, the /v1/rpc endpoint, responses, error codes, curl, PowerShell and Python examples.
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”Once the API is on (how to turn it on), Accovod starts a server on 127.0.0.1 and writes its details to a file:
~/Library/Application Support/Accovod/control.json%APPDATA%\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.
platformismacosorwindows.- Rotate the token with Automation → Regenerate token on macOS or the New token button on Windows.
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":{}}'$control = Get-Content "$env:APPDATA\Accovod\control.json" | ConvertFrom-Json
Invoke-RestMethod -Method Post -Uri "$($control.baseUrl)/v1/rpc" ` -Headers @{ Authorization = "Bearer $($control.token)" } ` -ContentType "application/json" ` -Body '{"method":"profiles.list","params":{}}'import json, os, pathlib, sys, urllib.request
# control.json: %APPDATA% on Windows, Application Support on macOSif sys.platform == "win32": path = pathlib.Path(os.environ["APPDATA"], "Accovod", "control.json")else: path = pathlib.Path("~/Library/Application Support/Accovod/control.json").expanduser()control = json.loads(path.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. |
SESSION_LOADING |
409 | macOS: the group is still loading from the cloud. Requests wait for it up to two minutes; after that, retry in a few seconds. |
SESSION_CHANGED |
409 | While the group was opening, the person or another request opened a different one. |
CLOUD_UNAVAILABLE |
502 | Accovod Cloud didn’t answer. Check the connection and retry. |
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. |
Windows specifics
Section titled “Windows specifics”The API contract on Windows is the same: the same methods, parameters, responses and error codes. Behavior differs only where the app itself works differently.
- Engines. A profile’s
engineischrome(the built-in Chromium) oredge(Microsoft Edge WebView2).browserfor a new profile ischrome,edge,firefox,mobile,autoornative. - Profile mode
profileModecan also beonDemand— a profile loads when first opened and then stays in memory (Load When Selected in settings). In theallandonDemandmodespage.navigateloads a profile in the background without switching the screen. - Changing the protection mode with
settings.setis saved at once but applies after Accovod restarts: the answer hasrestartRequired: true. - Page dialogs. While a site shows
alert,confirmorprompt, the page is paused. Calls answerDIALOG_OPENwith the dialog text, andpage.dialog(accept,promptText) answers it. You still see the dialog as usual. - A profile not on screen. The browser doesn’t deliver mouse hover and wheel events to a hidden tab, so there
page.hoverandpage.scrollfall back to script and say so (trusted: false).page.selectalways picks the option by script. Clicks, keys and typing are real in every case, and profiles are filled in parallel. - Deleting a profile. Cookies are erased at once; the profile folder with cache and LocalStorage goes when Accovod closes (
storageRemoved: falsewith a note). - Deleting a group keeps its backups in
cef\sessions— the way back after a mistake. page.uploadtakes files from Downloads, Desktop, Documents, Pictures, Videos and Music, and the same folders in OneDrive, with no extra setup. Add other folders to the MCP server’sACCOVOD_UPLOAD_ROOTSenvironment variable, separated by;.- No DevTools port is opened:
cdp.availableinapp.statusisfalse.
Extra error codes on Windows:
| Code | HTTP | Meaning |
|---|---|---|
DIALOG_OPEN |
409 | The page shows an alert, confirm or prompt. Answer it with page.dialog. |
NO_DIALOG |
409 | page.dialog was called, but no dialog is open. |
TARGET_MISMATCH |
400 | The ref points to the same element, but role, name or within don’t fit it — the request is wrong, not the page. |
NOT_EDITABLE / NOT_FOCUSABLE |
400 / 409 | page.fill got an element that isn’t a text field or doesn’t take focus. Nothing was typed. |
MULTIPLE_CONDITIONS |
400 | page.waitFor got more than one condition. |
IDEMPOTENCY_CONFLICT |
409 | The same idempotencyKey came with different parameters. |
BROWSER_TIMEOUT |
504 | The profile’s browser didn’t answer in time. |
When a page fails to load — a dead proxy, no DNS — page.navigate and page.reload answer loaded: false with the network code in netError, for example ERR_PROXY_CONNECTION_FAILED.
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.