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

Source: https://merca.getchrovia.com/en/docs/automation-api

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

1. Open the Merca desktop app and sign in.
2. Make sure your account has a paid plan.
3. Enable the automation API in the app's settings, copy the API key, and check the port.
4. Before launching a store, make sure the browser engine is ready and the store has a working node assigned.
5. 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:

```http
Authorization: Bearer <API_KEY>
```

For HTTP requests with a body, also send:

```http
Content-Type: application/json
```

The 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 `:passkeyId` with actual IDs.
- List endpoints do not support general pagination parameters. Activity lists return up to 200 recent entries.
- Store, node, and member `PATCH` requests preserve omitted fields. Clearing rules are described below. Do not use `null` in place of an omitted field.

Error response:

```json
{
  "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 `admin` account. A `member` needs an explicit store grant.
- Store `read` access allows launching the store and reading its saved credentials.
- Store `full` access 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.

```bash
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:

```json
{"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:

```bash
curl -sS http://127.0.0.1:9222/json/version
```

Connect 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:

```bash
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:

```http
GET /v1/platforms?platformId=<PLATFORM_ID>
```

Response shape:

```json
{
  "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.

```json
{
  "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_udp`
- `default_public_interface_only`
- `default_public_and_private_interfaces`
- `default`

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:

```json
{
  "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:

```http
PATCH /v1/stores/:id
```

```json
{
  "name": "Store B",
  "fingerprint": {"hardwareConcurrency": 4}
}
```

### 5.2 Store response objects

Each store in a list response contains:

- `id`, `name`, `platformId`, `siteIds`, `username`
- `nodeId`, `nodeBound`
- `fingerprint`
- `appearanceLabel`, `appearanceBackgroundColor`, `appearanceForegroundColor`
- `createdAt`, `updatedAt`, and optional `lastLaunchedAt`

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

```json
{
  "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](https://merca.getchrovia.com/en/docs/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

```http
POST /v1/stores/:id/command
```

Response 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.
- `close` only manages browsers started through `launch`, not those launched from the app UI or processes started by the caller using `command`.
- If no API launch is available to close, `close` returns `404` with code `not_running`. The first `close` request 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 |

```json
{
  "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 `ip` and `country`, or omit both. Supplying only one returns `node_probe_required`.
- Manually supplied geographic fields, such as `timezone`, require both `ip` and `country`. 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` |

```json
{
  "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:

```json
{"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:

```json
{
  "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:

```json
{
  "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:

```json
{"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:

```json
{
  "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 |

## Related guides

- [Add a network node](https://merca.getchrovia.com/en/docs/import-node)
- [Add a store](https://merca.getchrovia.com/en/docs/add-store)
- [Add a team member](https://merca.getchrovia.com/en/docs/add-member)
- [How store fingerprints work](https://merca.getchrovia.com/en/docs/store-fingerprint)
