Automation API
Use Merca's local HTTP API to manage stores, nodes, team members, and credentials, then launch browsers and connect over CDP for web automation.
Use Merca's automation API to manage stores, network nodes, team members, and saved credentials, and to launch store browsers.
Reading pages, clicking, typing, and taking screenshots require CDP or a compatible automation tool after the browser starts. These actions are not HTTP endpoints in this API.
1. Before you start
- Open the Merca desktop app and sign in.
- Make sure your account has a paid plan.
- Enable the automation API in the app's settings, copy the API key, and check the port.
- Before launching a store, make sure the browser engine is ready and the store has a working node assigned.
- Keep the desktop app running and connected to the internet. The API uses the app's signed-in session; it does not provide a separate sign-in endpoint.
Default connection settings:
| Setting | Default |
|---|---|
| HTTP base URL | http://127.0.0.1:43210 |
| Automation server | Disabled |
| Listen address | 127.0.0.1; changes to 0.0.0.0 when remote access is enabled |
| Port range | 1–65535, configurable in settings |
For remote access, replace 127.0.0.1 with the address of the computer running Merca. Browser processes, debugging ports, and local file paths returned by the API always belong to that computer, not the caller's computer.
2. Conventions
2.1 Authentication
Every API request except GET /health requires this header:
Authorization: Bearer <API_KEY>For HTTP requests with a body, also send:
Content-Type: application/jsonThe API key grants access to the local automation server. It does not replace account sign-in or grant additional account permissions.
2.2 Requests and responses
- Request bodies must be JSON objects, not arrays, strings, or
null. - Request bodies are limited to 256 KiB. An oversized request may have its connection closed directly.
- All successful endpoints listed here return
200, including creation and deletion. - Responses use JSON and include
Cache-Control: no-store. - Store, node, member, and credential path IDs are UUIDs. Invalid path IDs return
404. - Replace
:id,:passwordId,:otpId, and:passkeyIdwith actual IDs. - List endpoints do not support general pagination parameters. Activity lists return up to 200 recent entries.
- Store, node, and member
PATCHrequests preserve omitted fields. Clearing rules are described below. Do not usenullin place of an omitted field.
Error response:
{
"error": "Error description",
"code": "not_found"
}Handle errors using the HTTP status and code, not the wording or language of error.
2.3 Permissions
- Creating or deleting stores, accessing node endpoints, and managing the team require an owner or
adminaccount. Amemberneeds an explicit store grant. - Store
readaccess allows launching the store and reading its saved credentials. - Store
fullaccess also allows editing the store and its saved credentials. It does not grant team-management permissions. - Creating a regular member does not grant access to any store. Assign store access separately.
3. Quick start
These commands use Bash/zsh syntax. Replace the API key and store ID with your own values.
export MERCA_API_BASE='http://127.0.0.1:43210'
export MERCA_API_KEY='<API_KEY_FROM_MERCA_SETTINGS>'
curl -sS "$MERCA_API_BASE/health"
curl -sS "$MERCA_API_BASE/v1/engine" \
-H "Authorization: Bearer $MERCA_API_KEY"
curl -sS "$MERCA_API_BASE/v1/stores" \
-H "Authorization: Bearer $MERCA_API_KEY"
export STORE_ID='<STORE_UUID>'
curl -sS -X POST "$MERCA_API_BASE/v1/stores/$STORE_ID/launch" \
-H "Authorization: Bearer $MERCA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"startupUrl":"https://example.com","extraArgs":["--remote-debugging-port=9222"]}'A successful launch returns:
{"ok": true}This confirms that the browser process started, not that a page, website login, or CDP endpoint is ready. Once the debugging port is available, query it with:
curl -sS http://127.0.0.1:9222/json/versionConnect your automation tool using the returned webSocketDebuggerUrl, or the CDP HTTP address if your tool supports it. Port 43210 is the Merca API port; 9222 is the example browser debugging port. Use a different debugging port for each browser when running multiple stores in parallel.
Before using a member account for CDP page automation, turn off that member's “Disable developer tools” setting and restart the store browser.
Close the store launched through the API:
curl -sS -X POST "$MERCA_API_BASE/v1/stores/$STORE_ID/close" \
-H "Authorization: Bearer $MERCA_API_KEY"4. Service, account, and platforms
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /health | Server health; no authentication required | { "ok": true } |
| GET | /v1/engine | Browser engine status | { "ready": true, "version": "..." } |
| GET | /v1/session | Desktop sign-in session status | { "session": "active" } |
| GET | /v1/account/entitlements | Account entitlements | { "entitlements": { ... } } |
| GET | /v1/platforms | Platform and site catalog | { "platforms": [ ... ] } |
| GET | /v1/activity | Current account's recent activity | { "entries": [ ... ] } |
4.1 Engine and session status
The version returned by GET /v1/engine is the browser engine version, not the desktop app version. It is an empty string when unavailable. ready does not indicate whether any store is running or whether a debugging port is available.
GET /v1/session returns one of these session values:
| Value | Meaning |
|---|---|
active | The session is valid |
revoked | The session has been revoked |
unknown | The status could not be determined, for example because of a failed or timed-out request |
A successful /health response does not confirm account sign-in, permissions, or engine readiness.
4.2 Account entitlements
The entitlements object contains:
| Field | Type | Description |
|---|---|---|
tier | string | Plan identifier |
maxStores | number | Store limit |
usedStores | number | Stores currently counted toward the limit |
renewsAt | string or null | Renewal time |
cancelAt | string or null | Cancellation time |
status | string or null | Subscription status |
4.3 Platform catalog
Filter by a single platform with the optional platformId query parameter:
GET /v1/platforms?platformId=<PLATFORM_ID>Response shape:
{
"platforms": [
{
"id": "<PLATFORM_ID>",
"name": "Platform name",
"sites": [
{"id": "<SITE_ID>", "name": "Site name"}
]
}
]
}Names follow the app's current language. Use the returned IDs, not display names, when creating a store. Filtering by an unknown platform returns 404.
4.4 Activity
GET /v1/activity and GET /v1/members/:id/activity return the same structure: up to 200 entries, newest first.
{
"entries": [
{
"id": "<ACTIVITY_UUID>",
"action": "Activity type",
"details": {},
"createdAt": "2026-09-17T08:00:00.000Z"
}
]
}details contains action-specific fields with string or boolean values.
5. Stores
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /v1/stores | List stores visible to the current account | { "stores": [...] } |
| POST | /v1/stores | Create a store | { "store": {...} } |
| GET | /v1/stores/:id | Get store details | { "store": {...} } |
| PATCH | /v1/stores/:id | Update a store | { "store": {...} } |
| DELETE | /v1/stores/:id | Delete a store and its corresponding local data and cache | { "ok": true } |
5.1 Create and update fields
| Field | Type | Required on creation | Description |
|---|---|---|---|
name | string | Yes | Nonempty after trimming; up to 120 characters |
platformId | string | Yes | Platform ID; cannot be changed after creation |
siteIds | string[] | Yes | At least one site ID; updates replace the entire array |
username | string | No | Store login account; up to 320 characters; defaults to an empty string. Nonempty values must be unique among undeleted stores on the same platform within the team |
password | string | No | Store login password; up to 512 characters; defaults to an empty string |
nodeId | string | No | Node UUID; an empty string leaves the store unassigned or removes its assignment |
fingerprint | object | No | Fingerprint settings, listed below |
appearanceLabel | string | No | Appearance label; up to 120 characters; defaults to an empty string |
appearanceBackgroundColor | string | No | Appearance background color; defaults to an empty string |
appearanceForegroundColor | string | No | Appearance foreground color; defaults to an empty string |
You can create a store without a node, but a node must be assigned before launching it or generating a launch command.
The fingerprint object accepts:
| Field | Type | Creation default | Description |
|---|---|---|---|
browserVersion | string | "" | Reported version, up to 32 characters; empty or auto selects the highest available version matching the engine's major version |
hardwareConcurrency | number | Random | CPU thread count; integer from 1 to 256 |
cpuPerformance | number | From random preset | CPU performance tier; integer from 0 to 4 |
deviceMemory | number | Random | Reported memory: 2, 4, 8, 16, or 32 GB; does not allocate physical memory |
speechVoicesMode | string | override | none: no override; override: full override; append_google: append Google voices only |
fontAllowlistOverride | boolean | true | Apply the preset font allowlist for the host system |
disableCanvasNoise | boolean | false | Disable pixel noise in Canvas 2D readback and image exports; noise stays enabled by default. Does not affect WebGL, WebGPU, or other noise. Relaunch after changes |
webrtcIpPolicy | string | disable_non_proxied_udp | WebRTC IP policy |
Each creation randomly selects 8/12/16/20/24/32 CPU threads and 16/32 GB of memory. The random preset uses performance tier 3 for 8 threads and 4 otherwise. Explicit fields override the corresponding preset values. Updates preserve omitted fields, and launches do not randomize them again.
cpuPerformance tiers: 0 = Unknown, 1 = Low, 2 = Mid, 3 = High, 4 = Ultra.
Supported webrtcIpPolicy values:
disable_non_proxied_udpdefault_public_interface_onlydefault_public_and_private_interfacesdefault
UDP-enabled modes may expose direct IP addresses instead of the node IP. Close and relaunch the browser after changes.
Omitted fingerprint fields use the defaults above when creating a store. Updating fingerprint changes only the supplied subfields.
Example creation body; replace platform, site, and node IDs with values obtained from the API:
{
"name": "Store A",
"platformId": "<PLATFORM_ID>",
"siteIds": ["<SITE_ID>"],
"username": "seller@example.com",
"password": "<STORE_PASSWORD>",
"nodeId": "<NODE_UUID>",
"fingerprint": {
"hardwareConcurrency": 8,
"deviceMemory": 8,
"webrtcIpPolicy": "disable_non_proxied_udp"
}
}Example partial update:
PATCH /v1/stores/:id{
"name": "Store B",
"fingerprint": {"hardwareConcurrency": 4}
}5.2 Store response objects
Each store in a list response contains:
id,name,platformId,siteIds,usernamenodeId,nodeBoundfingerprintappearanceLabel,appearanceBackgroundColor,appearanceForegroundColorcreatedAt,updatedAt, and optionallastLaunchedAt
nodeBound indicates whether nodeId is set. It does not indicate node connectivity or browser status.
Detail, creation, and update responses also include:
| Field | Type | Description |
|---|---|---|
password | string | Store login password |
node | object or null | Assigned node summary, or null if none is assigned |
The node summary contains name, ip, country, timezone, and acceptLanguages. It does not include the proxy password; use the node endpoints for connection details.
Store login fields are separate from the saved website password collection in section 9.
6. Browser lifecycle
| Method | Path | Purpose | Success response |
|---|---|---|---|
| POST | /v1/stores/:id/launch | Launch the store browser | { "ok": true } |
| POST | /v1/stores/:id/command | Get a launch command without starting the browser | { "executable": "...", "args": [...] } |
| POST | /v1/stores/:id/close | Close a browser started through launch | { "ok": true } |
6.1 Launch options
launch and command accept the same body. Send {} if no custom options are needed.
| Field | Type | Required | Description |
|---|---|---|---|
startupUrl | string | No | URL to open at startup; an empty string is treated as unset |
extraArgs | string[] | No | Additional Chromium command-line arguments; defaults to an empty array |
{
"startupUrl": "https://example.com",
"extraArgs": ["--remote-debugging-port=9222"]
}Launches include the app's global launch arguments, followed by this call's extraArgs; an empty array does not clear the global arguments. extraArgs cannot override the store data directory or browser lifecycle arguments managed by Merca.
launch and command both enforce login protection. If the store's login account conflicts with the node's account records, the API returns 400 node_account_conflict. Review and resolve the account records in the app before retrying.
A successful launch only confirms browser startup. Calling it again may close and restart the store browser. Do not use it to poll browser status.
6.2 Launch command
POST /v1/stores/:id/commandResponse fields:
| Field | Type | Description |
|---|---|---|
executable | string | Path to the browser executable |
args | string[] | Complete launch arguments |
Pass executable and args directly to a process-spawning API. Do not concatenate them into a shell command or override the store data directory.
command does not start the browser, but it updates the store's last-launch time and activity records. If the store has a nonempty login account, it may also add a node account record, affecting whether other accounts can use the same IP on that platform. These effects occur even if you never execute the returned command.
For --remote-debugging-pipe, use command and have the caller start the process with the debugging pipes connected correctly. launch does not return pipes to the caller.
6.3 Lifecycle
- Keep the Merca desktop app running while using the browser.
closeonly manages browsers started throughlaunch, not those launched from the app UI or processes started by the caller usingcommand.- If no API launch is available to close,
closereturns404with codenot_running. The firstcloserequest can still succeed after the browser has exited on its own, so do not use this endpoint to check whether it is running. - Successful closure means a close request was issued, not that the process has exited.
- Each store uses its own local data directory. Do not launch multiple instances of the same store concurrently.
7. Network nodes
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /v1/nodes | List nodes without proxy passwords | { "nodes": [...] } |
| POST | /v1/nodes | Create a node | { "node": {...} } |
| GET | /v1/nodes/:id | Get node details, including its proxy password | { "node": {...} } |
| PATCH | /v1/nodes/:id | Update a node | { "node": {...} } |
| DELETE | /v1/nodes/:id | Delete a node | { "ok": true } |
Unassign the node from all stores before deleting it; otherwise the API returns 400 node_in_use.
7.1 Create and update fields
| Field | Type | Required on creation | Default or description |
|---|---|---|---|
name | string | No | Empty string; up to 120 characters |
protocol | string | No | http, https, or socks5; defaults to http |
host | string | Yes | Proxy hostname or IP address; up to 255 characters |
port | number | No | Integer from 1 to 65535; defaults to 8080 |
username | string | No | Empty string; up to 512 characters |
password | string | No | Empty string; up to 512 characters |
acceptProxyChain | boolean | No | Whether to use the app's configured proxy chain; defaults to true |
ip | string | No | Known exit IP; must be provided together with country |
country | string | No | Two-letter country or region code, such as us |
timezone | string | No | IANA timezone for manually supplied probe data, such as America/New_York |
region, city, asn, org | string | No | Additional manually supplied probe data |
latitude, longitude | number | No | Coordinates for manually supplied probe data |
{
"name": "US node A",
"protocol": "socks5",
"host": "proxy.example.com",
"port": 1080,
"username": "<PROXY_USERNAME>",
"password": "<PROXY_PASSWORD>",
"acceptProxyChain": true
}Usage constraints:
- An exit IP can only be added once, even across accounts. Duplicate additions return
node_already_added. - Provide both
ipandcountry, or omit both. Supplying only one returnsnode_probe_required. - Manually supplied geographic fields, such as
timezone, require bothipandcountry. You can update them without changing connection details. A nonempty timezone must be a valid IANA timezone; empty uses the host system timezone. - When updating connection details or probe data, the exit IP must remain the same as the original node's IP. Otherwise the API returns
node_ip_changed. Create a new node to use a different exit IP.
7.2 Node responses
Node details contain:
id, name, protocol, host, port, username, password, acceptProxyChain, ip, country, timezone, and acceptLanguages.
List responses omit password; creation, update, and detail responses include it. Additional geographic fields accepted in a request are not necessarily included in responses.
8. Team members and store access
8.1 Members
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /v1/members | List members | { "members": [...] } |
| POST | /v1/members | Create a member | { "member": {...}, "downloadUrl": "..." } |
| GET | /v1/members/:id | Get member details | { "member": {...} } |
| PATCH | /v1/members/:id | Update a member | { "member": {...}, "downloadUrl": "..." } |
| DELETE | /v1/members/:id | Delete a member | { "ok": true } |
| PUT | /v1/members/:id/disabled | Set the disabled state | { "member": {...} } |
| GET | /v1/members/:id/activity | Get member activity | { "entries": [...] } |
Member endpoints do not include the team owner. Member paths use the member object's id, not its userId.
Create and update fields:
| Field | Type | Required on creation | Description |
|---|---|---|---|
nickname | string | Yes | Display name; 1–32 characters |
username | string | Yes | Team login account; 2–32 ASCII letters, digits, or underscores; trimmed, converted to lowercase, and unique within the team |
password | string | Yes | Team login password; 8–128 characters; omitted or empty on update preserves the current password |
role | string | No | admin or member; defaults to member |
disableDevtools | boolean | No | Disable developer tools; defaults to true |
passwordRevealBlock | boolean | No | Restrict password display; defaults to true |
{
"nickname": "Operations 1",
"username": "operator_01",
"password": "<MEMBER_PASSWORD>",
"role": "member",
"disableDevtools": true,
"passwordRevealBlock": true
}Member objects contain id, userId, username, nickname, role, disableDevtools, passwordRevealBlock, disabled, and createdAt. They do not include the login password.
To disable a member, send:
{"disabled": true}Send false to enable the member again. Disabling a member revokes their existing login sessions. You cannot change your own role, delete yourself, or call the disabled-state endpoint on yourself.
8.2 Store access grants
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /v1/stores/:id/members | List eligible members and their current access | { "members": [...] } |
| PUT | /v1/stores/:id/members | Replace all explicit grants | { "members": [...] } |
These endpoints require an owner or administrator and a paid plan. The list contains eligible member accounts only. Owners and administrators already have management access and are not included.
Each result contains userId, username, nickname, and access. Access is read, full, or null for no grant.
Replacement body:
{
"grants": [
{"userId": "<MEMBER_USER_UUID>", "access": "read"},
{"userId": "<ANOTHER_MEMBER_USER_UUID>", "access": "full"}
]
}Use userId here, not the member id used in member endpoint paths. Each user must appear in the eligible member list and must not be repeated.
This replaces the complete grant collection; it does not append. Existing grants omitted from the request are removed. Send { "grants": [] } to clear all regular-member grants for the store.
9. Saved website credentials
Reading credentials requires store read access. Writing or deleting them requires full access.
9.1 Website passwords
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /v1/stores/:id/passwords | List summaries without passwords | { "credentials": [...] } |
| POST | /v1/stores/:id/passwords | Save a website password | { "ok": true } |
| GET | /v1/stores/:id/passwords/:passwordId | Get a credential, including its password | { "credential": {...} } |
| PATCH | /v1/stores/:id/passwords/:passwordId | Update an unlocked credential | { "ok": true } |
| DELETE | /v1/stores/:id/passwords/:passwordId | Delete an unlocked credential | { "ok": true } |
| PUT | /v1/stores/:id/passwords/:passwordId/locked | Lock or unlock a credential | { "ok": true } |
Save request:
{
"origin": "https://example.com",
"username": "seller@example.com",
"password": "<WEBSITE_PASSWORD>"
}| Field | Rules |
|---|---|
origin | Required HTTP/HTTPS URL; up to 2048 characters |
username | Required and nonempty; up to 512 characters |
password | Required and nonempty; up to 2048 characters |
PATCH accepts one or more of these fields and requires at least one. Empty strings are not allowed. signon_realm is generated automatically and is not accepted as a request field.
Summaries contain id, origin, signon_realm, username, and locked. A single-credential response also contains password.
Saving updates an existing unlocked entry with the same sign-on realm and username. Locked entries cannot be overwritten. Saving returns no credential ID; query the list afterward to obtain it.
Lock request:
{"locked": true}Send false to unlock. A lock prevents edits, deletion, and browser-sync overwrites. It does not prevent reading or using the password.
Changes are provided to the browser on a subsequent launch. Deletion does not change the website account or remove credentials already loaded by a running browser. Later browser sync may overwrite unlocked entries or recreate deleted ones.
9.2 TOTP codes
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /v1/stores/:id/otp | List entry metadata | { "entries": [...] } |
| POST | /v1/stores/:id/otp | Save or update a TOTP entry | { "entry": {...} } |
| DELETE | /v1/stores/:id/otp/:otpId | Remove an entry | { "ok": true } |
| GET | /v1/stores/:id/otp/:otpId/code | Generate the current code | { "id": "...", "code": "...", "expiresAt": "..." } |
Save fields:
| Field | Type | Required | Description |
|---|---|---|---|
secret | string | Yes | Base32 secret; up to 256 characters after removing whitespace and uppercasing; not an otpauth:// URL |
label | string | No | Up to 120 characters; derived from issuer and account when omitted, or Authenticator if both are empty |
issuer | string | No | Up to 120 characters; defaults to an empty string |
accountName | string | No | Up to 180 characters; defaults to an empty string |
algorithm | string | No | SHA1, SHA256, or SHA512; defaults to SHA1 |
digits | number | No | 6 or 8; defaults to 6 |
period | number | No | Period in seconds; integer from 15 to 120; defaults to 30 |
List and save responses contain metadata only: id, label, issuer, accountName, algorithm, digits, and period. They do not return the secret.
Within the same store, the same normalized secret updates or restores the existing entry. A different secret creates a new entry. Omitted metadata and settings use defaults on each save; they do not preserve existing values.
Example code response:
{
"id": "<OTP_UUID>",
"code": "012345",
"expiresAt": "2026-09-17T08:00:30.000Z"
}code is a string; preserve leading zeroes. Codes use the clock on the computer running Merca. expiresAt is a UTC ISO timestamp. The API does not submit the code. Saving or deleting an entry does not enable or disable two-step verification on the website.
9.3 Passkeys
| Method | Path | Purpose | Success response |
|---|---|---|---|
| GET | /v1/stores/:id/passkeys | List passkey metadata | { "entries": [...] } |
| DELETE | /v1/stores/:id/passkeys/:passkeyId | Remove a saved entry | { "ok": true } |
Each entry contains id, rpId, userName, and userDisplayName. Private keys are not returned.
The automation API has no endpoint for creating or importing passkeys. Deletion affects Merca's saved entry and subsequent launches. It does not unregister the credential on the website or remove copies already loaded by a running browser.
10. Common errors
| HTTP status | code | Meaning |
|---|---|---|
| 400 | body_invalid | Invalid JSON |
| 400 | body_not_object | The top-level request value is not an object |
| 400 | input_invalid | Fingerprint, grants, passwords, or TOTP fields are invalid |
| 400 | profile_name_required | Missing store name |
| 400 | platform_id_required | Missing platform ID |
| 400 | platform_id_immutable | Attempt to change a store's platform |
| 400 | site_ids_required / site_ids_invalid | Missing or invalid site collection |
| 400 | node_timezone_invalid | Nonempty node timezone is invalid |
| 400 | startup_url_invalid | Invalid startup URL type |
| 400 | extra_args_invalid | Arguments are not a string array or include a protected argument |
| 400 | node_host_required | Missing node host |
| 400 | node_port_invalid | Invalid node port |
| 400 | node_protocol_invalid | Unsupported proxy protocol |
| 400 | node_probe_required | Incomplete node probe data |
| 400 | node_probe_failed | Node check failed |
| 400 | node_ip_changed | Updated node exit IP differs from the original |
| 400 | node_account_conflict | The store's login account conflicts with the node's account records |
| 400 | node_in_use | The node is still assigned to a store and cannot be deleted |
| 400 | node_already_added | The exit IP has already been added |
| 400 | organization_username_taken | Another member in the team already uses this login account |
| 400 | store_username_taken | Another store on the same platform within the team already uses this login account |
| 400 | account_locked | The website password is locked and cannot be overwritten, edited, or deleted |
| 400 | store_members_invalid | Ineligible or duplicate member in a grant request |
| 400 | organization_member_disabled_invalid | disabled is not a boolean |
| 400 | otp_code_failed | Failed to generate a TOTP code |
| 401 | unauthorized | Missing or incorrect API key |
| 401 | not_authenticated | Desktop account is signed out or its session has expired |
| 403 | paid_plan_required | A paid plan is required |
| 403 | forbidden | The current account lacks permission |
| 403 | store_quota_exceeded | Store limit reached |
| 404 | not_found | Resource or route not found, or an invalid path ID |
| 404 | not_running | No API launch available to close |
| 405 | method_not_allowed | Unsupported HTTP method |
| 409 | profile_changed | Store details changed during launch; refresh and try again |
| 413 | body_too_large | Request body exceeds the limit; the connection may close directly |
| 500 | automation_api_error | Request processing failed |
| 503 | merca_console_session_not_ready | The desktop session is not ready |
| 503 | browser_engine_not_ready | The browser engine is not ready |
