---
title: Browser API — MegaIndex cloud browser for CDP automation
description: Connect to the MegaIndex cloud browser via Playwright, Puppeteer, and Chrome DevTools Protocol, configure a proxy, and purchase browser traffic.
keywords: browser api, cloud browser, megaindex, cdp, websocket, playwright, puppeteer, captcha
---

# MegaIndex Browser API

Browser API is a MegaIndex cloud browser for automating sites that need a real browser: JavaScript rendering, clicks, forms, scrolling, geo-dependent content, and CAPTCHA handling.

Your script connects to the remote browser via a CDP WebSocket URL and controls it through Playwright, Puppeteer, or another client that supports the Chrome DevTools Protocol (CDP). There is no need to run or maintain Chrome on your own server.

Browser API relies on two independent resources:

- **browser traffic** — cloud browser traffic, metered by MegaIndex in GB;
- **custom proxy** — the user's external proxy. MegaIndex does not sell, price, or meter traffic for this proxy.

A new user gets a Default account, a Default profile, 1 GB of test browser traffic, and a limited free IPv6 fallback for initial testing. In the current implementation the fallback may also apply to other profiles with `proxyMode: "none"`. Use your own proxy for production connections.

## What you can do with Browser API

- connect to a remote Chrome instance via CDP;
- use Playwright, Puppeteer, and other CDP clients;
- create browser accounts and separate browser profiles;
- save a custom proxy at the account or profile level;
- pass a custom proxy for a specific connection only;
- solve CAPTCHAs automatically or manually via the CDP `Captcha` interface;
- retrieve browser traffic usage history and statistics;
- purchase additional browser traffic using your MegaIndex balance.

## Key terms

**Browser account** — the primary cloud browser account. It holds the browser login, browser password, and shared connection settings.

**Browser profile** — a separate browser environment inside a browser account. Use different profiles for parallel processes.

**Default account** — the browser account named `Default browser account`, automatically available to a new user.

**Default profile** — the initial profile of the Default account.

**Browser traffic** — traffic transferred by the cloud browser. It is purchased and metered separately from the user's proxy traffic.

**Custom proxy** — the user's external HTTP, HTTPS, SOCKS4, or SOCKS5 proxy.

**CDP URL / `connectionUri`** — a WebSocket URL with access credentials for connecting to the cloud browser.

# Quick start

## 1. Check your test resources

On first connection, a user has access to:

- Default account;
- Default profile;
- 1 GB of test browser traffic;
- a limited free IPv6 fallback.

This is enough to verify a CDP connection without purchasing browser traffic or configuring your own proxy up front.

The IPv6 fallback is intended only for getting familiar with the service. Some sites do not support IPv6, block such traffic, or show different content. Configure a custom proxy for production use.

## 2. Get a ready-made CDP URL

Use the Default profile's CDP URL from the UI, or request `connectionUri` via the public API.

Do not publish `connectionUri`: it contains browser access credentials.

## 3. Connect via Playwright

Install Playwright:

```bash
npm install playwright
```

Save the CDP URL to an environment variable:

```bash
export MEGAINDEX_BROWSER_CDP_URL="PASTE_CDP_URL_HERE"
```

Create a `quick-start.js` file:

```javascript
const { chromium } = require('playwright');

async function main() {
  const connectionUri = process.env.MEGAINDEX_BROWSER_CDP_URL;

  if (!connectionUri) {
    throw new Error('MEGAINDEX_BROWSER_CDP_URL is not set');
  }

  const browser = await chromium.connectOverCDP(connectionUri);
  const context = browser.contexts()[0];
  const page = await context.newPage();

  await page.goto('https://example.com', {
    waitUntil: 'networkidle',
    timeout: 60_000,
  });

  console.log(await page.title());
  await page.screenshot({ path: 'browser-api-test.png', fullPage: true });
  await browser.close();
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

Run the example:

```bash
node quick-start.js
```

## 4. Connect via Puppeteer

Install Puppeteer Core:

```bash
npm install puppeteer-core
```

```javascript
const puppeteer = require('puppeteer-core');

async function main() {
  const connectionUri = process.env.MEGAINDEX_BROWSER_CDP_URL;

  if (!connectionUri) {
    throw new Error('MEGAINDEX_BROWSER_CDP_URL is not set');
  }

  const browser = await puppeteer.connect({
    browserWSEndpoint: connectionUri,
  });

  const page = await browser.newPage();
  await page.goto('https://example.com', {
    waitUntil: 'networkidle2',
    timeout: 60_000,
  });

  console.log(await page.title());
  await browser.disconnect();
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});
```

## 5. Switch to a custom proxy

For production connections, add your own proxy at the browser account level, browser profile level, or in the `connection` request.

MegaIndex does not meter proxy traffic. Its cost, limits, geography, and availability are determined by your proxy provider.

# Public HTTP API

## Base URL

Current test endpoint:

```text
http://89.108.119.8/test-browser-api/browser.php
```

The examples below use this variable:

```bash
BASE_URL="http://89.108.119.8/test-browser-api/browser.php"
```

## Authorization

Requests use the MegaIndex user's API key. You can obtain or reissue it on this page:

```text
/profile/api-key
```

After reissuing, the old key stops working.

The key is supported in four formats.

Query string:

```bash
curl "$BASE_URL?method=accounts&key=YOUR_API_KEY"
```

JSON body:

```bash
curl -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -d '{"method":"connection","key":"YOUR_API_KEY","accountId":123}'
```

`X-API-Key` header:

```bash
curl "$BASE_URL?method=accounts" \
  -H "X-API-Key: YOUR_API_KEY"
```

Bearer token:

```bash
curl "$BASE_URL?method=accounts" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

Bearer authentication has been confirmed by an actual request: a valid key in the `Authorization` header returns `200 OK` and the normal response for the selected method.

Do not place the API key in frontend code, public repositories, screenshots, or logs.

## Request format

The operation is passed in the `method` parameter. `endpoint` and `action` are also technically supported, but use `method` for consistency.

For JSON requests, send:

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

## Methods

| `method` | HTTP | Purpose |
|---|---|---|
| `prices` | `GET` | Get the price of browser traffic based on purchase volume. |
| `accounts` | `GET`, `POST`, `PUT`, `DELETE` | Manage browser accounts. |
| `profiles` | `GET`, `POST`, `PUT`, `DELETE` | Manage browser profiles. |
| `connection` | `GET`, `POST` | Get a WebSocket URI for a CDP connection. |
| `history` | `GET` | Get the Browser API operations history. |
| `statistics` | `GET` | Get browser traffic usage statistics. |
| `buy` | `POST` | Purchase browser traffic using the MegaIndex balance. |

The aliases `stats`, `buy-traffic`, and `traffic-buy` are supported for compatibility. Use the primary method names for new integrations.

# Browser traffic and billing

## What MegaIndex meters

MegaIndex meters only browser traffic — the cloud browser's network traffic. It is measured in GB and consumed independently of custom proxy traffic.

The custom proxy belongs to the user. MegaIndex does not display its balance, does not charge for it, and does not control the proxy provider's pricing.

In practice, this means that during a production connection, two resources may be consumed simultaneously:

1. browser traffic on MegaIndex;
2. proxy traffic at an external provider.

## Get prices

```bash
curl "$BASE_URL?method=prices&key=YOUR_API_KEY"
```

With an explicit currency:

```bash
curl "$BASE_URL?method=prices&currency=usd&key=YOUR_API_KEY"
```

The response contains an array of price tiers:

```json
[
  {
    "from": 1,
    "to": 9,
    "price": 5,
    "discount": 0,
    "oldPrice": 5,
    "currency": "usd"
  },
  {
    "from": 10,
    "to": 29,
    "price": 4,
    "discount": 20,
    "oldPrice": 5,
    "currency": "usd"
  },
  {
    "from": 10000,
    "to": 0,
    "price": 1.4,
    "discount": 72,
    "oldPrice": 5,
    "currency": "usd"
  }
]
```

Tier fields:

| Field | Type | Description |
|---|---|---|
| `from` | integer | Minimum purchase volume in GB, inclusive. |
| `to` | integer | Maximum volume in GB, inclusive. A value of `0` means there is no upper bound. |
| `price` | number | Price per GB after the volume discount is applied. |
| `oldPrice` | number | Base price per GB without a discount. |
| `discount` | number | Discount relative to the base price, in percent. Can be fractional. |
| `currency` | string | Price currency. The confirmed response uses `usd`. |

When purchasing, the API automatically selects the price tier based on the `flow` value. For example, a purchase of 10 to 29 GB uses the `4 USD` per GB price, while a purchase of 10000 GB or more uses `1.4 USD` per GB.

## Buy browser traffic

```bash
curl -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "buy",
    "key": "YOUR_API_KEY",
    "flow": 10,
    "idempotencyKey": "browser-traffic-order-1001"
  }'
```

Parameters:

| Field | Type | Required | Description |
|---|---|---:|---|
| `flow` | integer | yes | The number of GB to purchase. Current allowed range: 1 to 100000. |
| `idempotencyKey` | string | yes | A unique operation key that protects against double charging. |

To resend the same order, use the same `idempotencyKey`. For a new purchase, create a new key.

Example response:

```json
{
  "status": "OK",
  "flow": 10,
  "financeId": 23082035,
  "price": {
    "amount": 50,
    "pricePerGb": 5,
    "oldPrice": 5,
    "discount": 0,
    "currency": "usd"
  },
  "balance": {
    "before": 100,
    "after": 50,
    "currency": "usd"
  },
  "traffic": {
    "totalKb": 10485760,
    "usedKb": 0,
    "availableKb": 10485760,
    "totalGb": 10,
    "usedGb": 0,
    "availableGb": 10
  }
}
```

In a successful response:

- `flow` — the purchased volume in GB;
- `financeId` — the MegaIndex financial operation ID;
- `price.amount` — the total purchase cost;
- `price.pricePerGb` — the applied price per GB;
- `balance.before` and `balance.after` — the balance before and after the charge;
- `traffic` — the updated total, used, and available browser traffic in KB and GB.

Confirmed ratio: `1 GB = 1048576 KB`.

If a request with the same `idempotencyKey` has already been processed, the funds are not charged again:

```json
{
  "status": "OK",
  "duplicate": true,
  "flow": 10,
  "financeId": 23082035,
  "price": {
    "amount": 50,
    "pricePerGb": 5,
    "oldPrice": 5,
    "discount": 0,
    "currency": "usd"
  },
  "balance": {
    "current": 50,
    "currency": "usd"
  }
}
```

In the repeat response:

- `duplicate: true` confirms the operation was already processed;
- `financeId` matches the ID of the original purchase;
- no repeat charge occurs and no additional browser traffic is credited;
- instead of `balance.before`/`balance.after`, `balance.current` is returned;
- the `traffic` block is absent.

If crediting browser traffic did not complete, the charge to the MegaIndex balance is rolled back.

# Browser accounts

## Get the list of accounts

```bash
curl "$BASE_URL?method=accounts&key=YOUR_API_KEY"
```

Abridged example response:

```json
{
  "status": "OK",
  "project": {
    "id": 2,
    "code": "megaindex",
    "name": "MegaIndex"
  },
  "count": 1,
  "maxAccounts": 10,
  "unlimitedAccounts": false,
  "data": [
    {
      "id": 123,
      "login": "REDACTED_BROWSER_LOGIN",
      "password": "REDACTED_BROWSER_PASSWORD",
      "name": "Default browser account",
      "status": 1,
      "country": "en",
      "defaultProfileId": 456,
      "proxyMode": "none",
      "customProxy": null,
      "profile": {
        "id": 456,
        "accountId": 123,
        "profileId": "p0123456789abcdef0123456789abcdef",
        "isDefault": true,
        "name": "Default profile",
        "proxyMode": "inherit",
        "usedKb": 1075,
        "usedGb": 0.001,
        "requestCount": 8,
        "status": 1
      },
      "profilesCount": 1,
      "usedKb": 1075,
      "usedGb": 0.001,
      "requestCount": 8,
      "maxProfiles": 1000,
      "unlimitedProfiles": false,
      "connection": {
        "host": "browser.example.com:9222",
        "username": "REDACTED_USERNAME",
        "password": "REDACTED_BROWSER_PASSWORD",
        "profileId": "p0123456789abcdef0123456789abcdef"
      },
      "connectionUri": "REDACTED_CONNECTION_URI",
      "createdAt": "2026-07-23 17:53:21",
      "updatedAt": "2026-07-23 17:53:21"
    }
  ]
}
```

Key fields:

- `count` — the current number of browser accounts;
- `maxAccounts` — the maximum number of accounts, 10 by default;
- `unlimitedAccounts` — whether the quantity limit is disabled;
- `defaultProfileId` — the numeric internal ID of the Default profile record;
- `profile.profileId` — the string profile ID used when requesting `connectionUri` and in the CDP URL;
- `profilesCount` and `maxProfiles` — the current and maximum number of profiles in the account;
- `usedKb`, `usedGb`, and `requestCount` — aggregate account statistics;
- `profile.isDefault: true` — marks the Default profile;
- `profile.proxyMode: "inherit"` — the profile inherits the account's proxy settings.

Do not confuse the numeric `profile.id` with the string `profile.profileId`. Use the string `profileId` for `connection`.

The response also contains service objects `project`, `serviceUser`, `proxySettings`, and a detailed `connection` object. These do not need to be modified on the client side.

## Create an account

A browser account can be created without a saved proxy. In this case, save the custom proxy later, or pass it when requesting `connectionUri`. The limited IPv6 fallback is available only for initial testing.

```bash
curl -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "accounts",
    "key": "YOUR_API_KEY",
    "name": "Main browser account"
  }'
```

A successful response contains `status`, `project`, and the created `account` object. A Default profile is created automatically along with the account:

- `account.proxyMode` is `none`;
- `account.customProxy` is `null`;
- `profile.isDefault` is `true`;
- `profile.name` is `Default profile`;
- `profile.proxyMode` is `inherit`;
- `account.defaultProfileId` matches the numeric `profile.id`;
- statistics for the new account/profile start at zero;
- `profile.lastUsedAt` is an empty string before first use.

Under the default limit, you can create up to 10 accounts. Each account can hold up to 1000 profiles. The actual limits are returned in `maxAccounts`, `unlimitedAccounts`, `maxProfiles`, and `unlimitedProfiles`.

The `name` field sets the browser account's display name and is returned in `account.name`.

## Update, reset password, and delete an account

The `accounts` method supports `PUT` and `DELETE`.

Deleting an account:

```bash
curl -X DELETE "$BASE_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "accounts",
    "key": "YOUR_API_KEY",
    "id": 123
  }'
```

Successful response:

```json
{
  "status": "OK",
  "project": {
    "id": 2,
    "code": "megaindex",
    "name": "MegaIndex"
  }
}
```

# Browser profiles

## Get the list of profiles

```bash
curl "$BASE_URL?method=profiles&accountId=123&page=1&limit=50&key=YOUR_API_KEY"
```

Parameters:

| Parameter | Type | Required | Description |
|---|---|---:|---|
| `accountId` | integer | yes | The numeric browser account ID. |
| `page` | integer | no | Page number, starting at 1. |
| `limit` | integer | no | Maximum number of profiles per page. |

Abridged example response:

```json
{
  "status": "OK",
  "project": {
    "id": 2,
    "code": "megaindex",
    "name": "MegaIndex"
  },
  "maxProfiles": 1000,
  "unlimitedProfiles": false,
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 1,
    "pages": 1
  },
  "data": [
    {
      "id": 456,
      "accountId": 123,
      "profileId": "p0123456789abcdef0123456789abcdef",
      "isDefault": true,
      "name": "Default profile",
      "zone": "scraping_browser",
      "country": "en",
      "proxyMode": "none",
      "customProxy": null,
      "profileData": {
        "comment": ""
      },
      "usedKb": 4677,
      "usedGb": 0.0045,
      "requestCount": 26,
      "status": 1,
      "source": "frontend_auto",
      "connectionUri": "REDACTED_CONNECTION_URI",
      "createdAt": "2026-08-05 10:34:49",
      "updatedAt": "2026-08-05 14:48:02",
      "lastUsedAt": "2026-08-05 14:48:02",
      "deletedAt": ""
    }
  ]
}
```

The `pagination` object contains the current page, the configured limit, the total number of profiles, and the number of pages.

`isDefault: true` means the profile is the Default profile for its account. It does not determine the proxy mode: a Default profile can be returned with either `proxyMode: "inherit"` or `proxyMode: "none"`, depending on the settings and how the account/profile was created.

The `connection` and `connectionUri` fields contain connection data. Do not publish them or write them to open logs.

## Create a profile

```bash
curl -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "profiles",
    "key": "YOUR_API_KEY",
    "accountId": 123,
    "name": "Google SERP checks"
  }'
```

Use different profiles for parallel CDP connections.

The `name` field sets the profile's display name and is returned in `profile.name`. The string `profileId` field is used in the external API and the CDP URL, while the numeric `id` is the internal record ID.

Successfully creating a custom profile returns `status`, `project`, and a `profile` object. The new profile:

- gets a server-generated numeric `id` and a string `profileId`;
- has `isDefault: false`;
- uses `proxyMode: "inherit"` by default;
- starts with zero `usedKb`, `usedGb`, and `requestCount`;
- has `source: "frontend"`;
- has an empty `lastUsedAt` until its first connection.

For comparison, an automatically created Default profile has `isDefault: true` and `source: "frontend_auto"`.

The default limit is 1000 profiles per account. `maxProfiles` and `unlimitedProfiles` are returned both in the account object and at the top level of the profile list response.

# Custom proxy

## Is a proxy required

A browser account and profile can be created without a saved proxy. A custom proxy is required for a production browser connection.

The exception is the limited free IPv6 fallback for initial testing. In the confirmed response, it applied to the Default profile of a regular user account with `proxyMode: "none"`, not only to the system Default account. A profile with `proxyMode: "inherit"` also remains without a saved proxy if the parent account is in `none` mode.

When the fallback is available, a `connection` request without `customProxy` returns `200 OK` and a ready `connectionUri`. In that case, `connection.username` does not contain a `-proxy-` segment, and the top-level `customProxy` field is `null`.

Do not use `proxySettings.exists` to determine whether a custom proxy is present. In the confirmed response, this field was `true` for a profile even though `proxyMode` was `none`, `customProxy` was `null`, and the connection was formed without a `-proxy-` segment.

## `customProxy` format

```json
{
  "type": "http",
  "host": "proxy.example.com",
  "port": 8000,
  "login": "proxy_user",
  "password": "proxy_password"
}
```

| Field | Type | Required | Description |
|---|---|---:|---|
| `type` | string | yes | `http`, `https`, `socks4`, or `socks5`. |
| `host` | string | yes | Proxy host or IP address. |
| `port` | integer | yes | Proxy port. |
| `login` | string | no | Login, if the proxy requires authorization. |
| `password` | string | no | Password, if the proxy requires authorization. |

The actual exit country is determined by the custom proxy itself.

## Proxy priority

Assumed selection order:

1. custom proxy passed for a specific `connectionUri`;
2. custom proxy saved on the browser profile;
3. custom proxy saved on the browser account;
4. limited IPv6 fallback, if it is currently available and, after inheritance is applied, the profile has no saved proxy.

# Getting `connectionUri`

## Connecting with a saved proxy

```bash
curl "$BASE_URL?method=connection&accountId=123&key=YOUR_API_KEY"
```

For a specific profile:

```bash
curl "$BASE_URL?method=connection&accountId=123&profileId=PROFILE_ID&key=YOUR_API_KEY"
```

## Connecting with a proxy for the current session

```bash
curl -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "connection",
    "key": "YOUR_API_KEY",
    "accountId": 123,
    "customProxy": {
      "type": "http",
      "host": "proxy.example.com",
      "port": 8000,
      "login": "proxy_user",
      "password": "proxy_password"
    }
  }'
```

Example response:

```json
{
  "status": "OK",
  "connectionUri": "REDACTED_CONNECTION_URI",
  "connection": {
    "scheme": "ws",
    "host": "cb.2captcha.com:9222",
    "username": "REDACTED_CONNECTION_USERNAME",
    "password": "REDACTED_BROWSER_PASSWORD"
  },
  "account": {
    "id": 123,
    "name": "Main browser account",
    "proxyMode": "none"
  },
  "profile": {
    "id": 456,
    "accountId": 123,
    "profileId": "p0123456789abcdef0123456789abcdef",
    "isDefault": true,
    "name": "Default profile",
    "proxyMode": "none"
  },
  "customProxy": {
    "type": "http",
    "host": "proxy.example.com",
    "port": 8000,
    "login": "REDACTED_PROXY_LOGIN",
    "password": "REDACTED_PROXY_PASSWORD"
  }
}
```

Getting the URI by itself does not start a browser session. The session starts once a CDP client connects.

The custom proxy passed for the connection is returned in the top-level `customProxy` field. In the confirmed response it was not saved to the account or profile: both objects retained `proxyMode: "none"` and `customProxy: null`. In other words, a proxy passed in the request applies to the resulting connection but does not become a saved account/profile setting.

`connection.username` contains a `-proxy-{encodedProxy}` segment. The `encodedProxy` value is a Base64URL representation of the full proxy URL, including credentials. Base64URL is a reversible encoding, not encryption.

Treat the following as secret and do not log them:

- the entire `connectionUri`;
- `connection.username`;
- `connection.password`;
- the browser login and password inside the nested `account`;
- the entire top-level `customProxy` object, if it contains credentials.

The `connection` response contains the full `account` and `profile` objects that were selected. Their IDs match the `accountId` and `profileId` passed in the request.

## Connecting without a custom proxy

If the account/profile has `proxyMode: "none"` and the test IPv6 fallback is available, a request without `customProxy` succeeds:

```json
{
  "status": "OK",
  "connectionUri": "REDACTED_CONNECTION_URI",
  "connection": {
    "scheme": "ws",
    "host": "cb.2captcha.com:9222",
    "username": "REDACTED_CONNECTION_USERNAME_WITHOUT_PROXY_SEGMENT",
    "password": "REDACTED_BROWSER_PASSWORD"
  },
  "account": {
    "id": 123,
    "proxyMode": "none",
    "customProxy": null
  },
  "profile": {
    "accountId": 123,
    "profileId": "p0123456789abcdef0123456789abcdef",
    "isDefault": true,
    "proxyMode": "none",
    "customProxy": null
  },
  "customProxy": null
}
```

This connection uses the limited IPv6 fallback. The absence of `-proxy-` in the username confirms that no custom proxy is embedded in the URI.

Do not use the fallback as a permanent production configuration: its availability is limited and it can be disabled. Pass a custom proxy for production scenarios.

# The CDP `Captcha` interface

The CDP functionality and CAPTCHA solving in MegaIndex match the 2Captcha Browser API. After connecting to the cloud browser, you can use the standard Playwright/Puppeteer capabilities plus the additional CDP domain `Captcha`.

CAPTCHA solving is included in Browser API and is not billed separately. MegaIndex charges only for browser traffic consumed by the cloud browser.

## Purpose

The `Captcha` CDP domain lets you control CAPTCHA solving on the cloud browser's current tab.

Two modes are available:

1. Automatic solving after the page loads.
2. Manual triggering via the `Captcha.solve` command.

CDP events let you track CAPTCHA detection, submission to the service, successful solving, and errors.

## Connecting to a CDP session

### Playwright

```javascript
import { chromium } from 'playwright';

const browser = await chromium.connectOverCDP(connectionUri);
const context = browser.contexts()[0];
const page = await context.newPage();
const session = await context.newCDPSession(page);
```

### Puppeteer

```javascript
import puppeteer from 'puppeteer-core';

const browser = await puppeteer.connect({
  browserWSEndpoint: connectionUri
});

const page = await browser.newPage();
const session = await page.target().createCDPSession();
```

## CDP data types

### `CaptchaOptions`

A single element of the `options` array. Usually a single object is passed:

```json
{
  "type": "*",
  "submitForm": false,
  "selector": ".captcha-container",
  "detectSelector": ".captcha-container",
  "responseSelector": "textarea[name=\"g-recaptcha-response\"]"
}
```

| Field | Type | Description |
|---|---|---|
| `type` | string | CAPTCHA type. Pass `*` for automatic detection. |
| `submitForm` | boolean | Submit the form after receiving the token. |
| `submitSelector` | string | CSS selector for the form's submit button. |
| `selector` | string | CSS selector for the CAPTCHA container. |
| `detectSelector` | string | CSS selector used to detect the CAPTCHA. |
| `responseSelector` | string | CSS selector of the field to write the token into. |
| `sitekeyAttributes` | string[] | Attributes from which `sitekey` can be obtained. |
| `actionAttributes` | string[] | Attributes from which `action` for reCAPTCHA v3 can be obtained. |

### `SolveResult`

The response of the `Captcha.solve` command is returned in the `result` field:

```json
{
  "result": {
    "status": "solveFinished",
    "token": "03AGdBq26..."
  }
}
```

| Field | Type | Description |
|---|---|---|
| `status` | string | `solveFinished`, `solveFailed`, `notDetected`, or `invalid`. |
| `token` | string | The token on a successful solve. |
| `errorMessage` | string | Error text on an unsuccessful solve. |

## CDP commands

### `Captcha.setAutoSolve`

Enables or disables automatic CAPTCHA solving on the tab:

```javascript
await session.send('Captcha.setAutoSolve', {
  autoSolve: true,
  options: [
    {
      type: '*'
    }
  ]
});
```

| Parameter | Type | Required | Description |
|---|---|---:|---|
| `autoSolve` | boolean | yes | `true` — solve automatically; `false` — use only `Captcha.solve`. |
| `options` | `CaptchaOptions[]` | no | CAPTCHA detection and solving settings. |

On success, the command returns no body.

Possible CDP error:

```text
No active frame
```

### `Captcha.solve`

Triggers an explicit CAPTCHA solve on the current tab:

```javascript
const response = await session.send('Captcha.solve', {
  detectTimeout: 15000,
  options: [
    {
      type: '*'
    }
  ]
});

console.log(response.result.status);
console.log(response.result.token);
```

| Parameter | Type | Required | Description |
|---|---|---:|---|
| `detectTimeout` | integer | no | CAPTCHA detection timeout in milliseconds. The internal response limit is approximately `detectTimeout + 10s`; about `60s` is used if the parameter is omitted. |
| `options` | `CaptchaOptions[]` | no | CAPTCHA detection and solving parameters. |

Successful solve:

```json
{
  "result": {
    "status": "solveFinished",
    "token": "03AGdBq26..."
  }
}
```

CAPTCHA not found:

```json
{
  "result": {
    "status": "notDetected"
  }
}
```

Possible CDP errors without a `SolveResult`:

```text
No active frame
Captcha.solve timed out waiting for extension response
```

## CDP events

Subscribing in Playwright:

```javascript
session.on('Captcha.detected', () => {
  console.log('CAPTCHA detected');
});

session.on('Captcha.waitForSolve', () => {
  console.log('CAPTCHA sent to solver');
});

session.on('Captcha.solveFinished', () => {
  console.log('CAPTCHA solved');
});

session.on('Captcha.solveFailed', () => {
  console.log('CAPTCHA solve failed');
});
```

| Event | Description |
|---|---|
| `Captcha.detected` | A CAPTCHA was detected on the page. |
| `Captcha.waitForSolve` | The request was sent to the service and a response is being awaited. |
| `Captcha.solveFinished` | The CAPTCHA was solved successfully. |
| `Captcha.solveFailed` | Solving failed with an error. |

Event chain in automatic mode:

```text
Captcha.detected → Captcha.waitForSolve → Captcha.solveFinished | Captcha.solveFailed
```

Events are meant for tracking progress. The token is returned only in the response of the `Captcha.solve` command.

## Recommended scenarios

### Automatic CAPTCHA solving

Use automatic mode when the browser should solve CAPTCHAs on its own after the page loads:

```javascript
await session.send('Captcha.setAutoSolve', {
  autoSolve: true,
  options: [{ type: '*' }]
});

const solved = new Promise((resolve, reject) => {
  session.once('Captcha.solveFinished', resolve);
  session.once('Captcha.solveFailed', reject);
});

await page.goto('https://example.com');
await solved;
```

Recommended order:

```text
Captcha.setAutoSolve({ autoSolve: true, options })
  → navigate to the page with the CAPTCHA
  → wait for Captcha.solveFinished or Captcha.solveFailed
```

In automatic mode, you do not need to retrieve or insert the token yourself. It is written on the page and, if configured, the form is submitted automatically via `responseSelector`, `submitForm`, and `submitSelector`. The events report readiness but do not carry the token.

### Manual CAPTCHA solving

Use manual triggering to control when solving starts and to obtain the token:

```javascript
await session.send('Captcha.setAutoSolve', {
  autoSolve: false,
  options: [{ type: '*' }]
});

await page.goto('https://example.com');
await page.waitForTimeout(5000);

const { result } = await session.send('Captcha.solve', {
  detectTimeout: 15000,
  options: [{ type: '*' }]
});

if (result.status === 'solveFinished') {
  console.log('Token:', result.token);
} else {
  console.log('Captcha solve status:', result.status, result.errorMessage);
}
```

Recommended order:

```text
Captcha.setAutoSolve({ autoSolve: false, options })
  → navigation
  → 3-5 second pause to let the widget register
  → Captcha.solve({ detectTimeout, options })
  → check result.status
```

Do not call `Captcha.solve` from the `Captcha.detected` handler. For manual mode, a single call after a short pause is sufficient.

## CDP timeouts

| Timeout | Recommendation |
|---|---|
| Internal browser timeout | `detectTimeout + 10s`, or about `60s` if `detectTimeout` is not passed. |
| Client timeout | At least `detectTimeout + 120-180s`, to account for the wait for a solve. |
| Maximum browser session duration | 30 minutes. Choose timeouts so that solving does not exceed the session. |

If your CDP client has its own timeout for `session.send`, increase it for `Captcha.solve`, otherwise the client may stop waiting before the service responds.

## Full example: MegaIndex Browser API + Playwright + auto-solve

```javascript
import { chromium } from 'playwright';

const API_URL = 'http://89.108.119.8/test-browser-api/browser.php';
const API_KEY = process.env.MEGAINDEX_API_KEY;

async function createConnectionUri() {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      method: 'connection',
      key: API_KEY,
      accountId: 123,
      profileId: 'p0123456789abcdef0123456789abcdef',
      customProxy: {
        type: 'http',
        host: 'proxy.example.com',
        port: 8080,
        login: 'proxyuser',
        password: 'proxypass'
      }
    })
  });

  const data = await response.json();

  if (data.status !== 'OK') {
    throw new Error(`${data.errorCode}: ${data.error}`);
  }

  return data.connectionUri;
}

const connectionUri = await createConnectionUri();
const browser = await chromium.connectOverCDP(connectionUri);
const context = browser.contexts()[0];
const page = await context.newPage();
const session = await context.newCDPSession(page);

session.on('Captcha.detected', () => console.log('CAPTCHA detected'));
session.on('Captcha.waitForSolve', () => console.log('Waiting for MegaIndex'));
session.on('Captcha.solveFinished', () => console.log('CAPTCHA solved'));
session.on('Captcha.solveFailed', () => console.log('CAPTCHA solve failed'));

await session.send('Captcha.setAutoSolve', {
  autoSolve: true,
  options: [{ type: '*' }]
});

await page.goto('https://example.com');
```

Do not store the API key and proxy credentials directly in your production application's source code. Use environment variables or a secrets store.

## Full example: manual `Captcha.solve`

```javascript
import { chromium } from 'playwright';

const connectionUri = process.env.MEGAINDEX_BROWSER_CDP_URL;
const browser = await chromium.connectOverCDP(connectionUri);
const context = browser.contexts()[0];
const page = await context.newPage();
const session = await context.newCDPSession(page);

await session.send('Captcha.setAutoSolve', {
  autoSolve: false,
  options: [{ type: '*' }]
});

await page.goto('https://example.com');
await page.waitForTimeout(5000);

const { result } = await session.send('Captcha.solve', {
  detectTimeout: 15000,
  options: [{ type: '*' }]
});

switch (result.status) {
  case 'solveFinished':
    console.log('CAPTCHA token:', result.token);
    break;
  case 'solveFailed':
    console.log('CAPTCHA solve failed:', result.errorMessage);
    break;
  case 'notDetected':
    console.log('CAPTCHA was not detected on the page');
    break;
  case 'invalid':
    console.log('Invalid solve request:', result.errorMessage);
    break;
  default:
    console.log('Unknown CAPTCHA status:', result.status);
}
```

## Integration recommendations

1. Obtain `connectionUri` via `method=connection` rather than assembling the WebSocket URL manually.
2. Always pass a custom proxy for production scenarios, either via the connection request or via saved account/profile settings.
3. Use separate profiles for stable isolation across different scenarios.
4. If you only need confirmation that the CAPTCHA was passed, use automatic mode and events.
5. If you need the token, use manual `Captcha.solve`.
6. Increase the CDP client timeout for manual solving.
7. Check `result.status`, not just whether the command returned a response.
8. For diagnostics, log events, but do not log tokens, `connectionUri`, `connection.username`, the browser password, or proxy credentials.

There is no separate balance or pricing for CAPTCHA solving: only browser traffic is billed.

# History and statistics

## History

```bash
curl "$BASE_URL?method=history&page=1&limit=50&key=YOUR_API_KEY"
```

The method returns the browser traffic purchase history:

```json
{
  "status": "OK",
  "project": {
    "id": 2,
    "code": "megaindex",
    "name": "MegaIndex"
  },
  "data": [
    {
      "id": 10,
      "flow": 1,
      "trafficKb": 1048576,
      "price": 5,
      "amount": 5,
      "valute": "usd",
      "status": 1,
      "createdAt": "2026-08-05 16:20:19"
    }
  ]
}
```

| Field | Type | Description |
|---|---|---|
| `id` | integer | Purchase operation ID. |
| `flow` | integer | Purchased browser traffic volume in GB. |
| `trafficKb` | integer | The same volume in KB. One GB equals 1048576 KB. |
| `price` | number | Price per GB. |
| `amount` | number | Total operation amount: `flow × price`. |
| `valute` | string | Operation currency. The API field is named exactly `valute`. |
| `status` | integer | Numeric operation status. |
| `createdAt` | string | Operation creation date and time in `YYYY-MM-DD HH:mm:ss` format. |

In the confirmed response, operations are ordered from newest to oldest.

## Browser traffic statistics

```bash
curl "$BASE_URL?method=statistics&key=YOUR_API_KEY"
```

Abridged example response:

```json
{
  "status": "OK",
  "project": {
    "id": 2,
    "code": "megaindex",
    "name": "MegaIndex"
  },
  "period": {
    "dateFrom": "2026-07-06",
    "dateTo": "2026-08-05"
  },
  "traffic": {
    "totalKb": 3145728,
    "usedKb": 0,
    "availableKb": 3145728,
    "totalGb": 3,
    "usedGb": 0,
    "availableGb": 3
  },
  "usage": {
    "trafficKb": 5752,
    "trafficGb": 0.0055,
    "requestCount": 34
  },
  "accounts": [
    {
      "id": 123,
      "login": "REDACTED_BROWSER_LOGIN",
      "name": "Default browser account",
      "status": 1,
      "trafficKb": 1075,
      "trafficGb": 0.001,
      "requestCount": 8,
      "lastUsedAt": "2026-08-05 10:00:00"
    }
  ],
  "profileUsage": {
    "trafficKb": 5752,
    "trafficGb": 0.0055,
    "requestCount": 34
  },
  "profiles": [
    {
      "id": 456,
      "accountId": 123,
      "profileId": "p0123456789abcdef0123456789abcdef",
      "name": "Default profile",
      "trafficKb": 1075,
      "trafficGb": 0.001,
      "requestCount": 8,
      "totalUsedKb": 1075,
      "totalUsedGb": 0.001,
      "totalRequestCount": 8,
      "periodLastUsedAt": "2026-08-05 10:00:00",
      "lastUsedAt": "2026-08-05 10:39:02"
    }
  ]
}
```

Main blocks:

- `period` — the period over which the usage metrics were calculated;
- `traffic` — the total purchased, used, and available browser traffic volume;
- `usage` — total consumption and request count for the selected period;
- `accounts` — consumption for the period, grouped by browser account;
- `profileUsage` — total profile consumption for the period;
- `profiles` — period and cumulative metrics for individual profiles.

In the `profiles` objects, the `trafficKb`, `trafficGb`, and `requestCount` fields relate to the selected period, while `totalUsedKb`, `totalUsedGb`, and `totalRequestCount` contain cumulative values.

In the confirmed response, without explicit dates the API selected the period from July 6 to August 5, 2026.

# Errors

All errors are returned as JSON:

```json
{
  "errorId": 1,
  "errorCode": "ERROR_WRONG_USER_KEY",
  "error": "API key is invalid."
}
```

| `errorCode` | HTTP | Meaning |
|---|---:|---|
| `ERROR_KEY_DOES_NOT_EXIST` | 401 | API key not provided. |
| `ERROR_WRONG_USER_KEY` | 401 | API key not found in MegaIndex. |
| `ERROR_ACCOUNT_SUSPENDED` | 403 | The account is disabled. |
| `ERROR_METHOD` | 405 | Unsupported HTTP method. |
| `ERROR_NO_SUCH_METHOD` | 404 | Unknown API method. |
| `ERROR_FLOW_AMOUNT` | 400 | Invalid number of GB. |
| `ERROR_IDEMPOTENCY_KEY` | 400 | Invalid idempotency key. |
| `ERROR_INSUFFICIENT_FUNDS` | 400 | Insufficient funds in the MegaIndex balance. |
| `ERROR_GB_PRICE` | 400 | Could not determine the browser traffic price. |
| `ERROR_CONNECTION_URI` | 404 | No data available to connect to the browser. |
| `ERROR_BROWSER_BACKEND_UNAVAILABLE` | 502 | The browser backend is temporarily unavailable. |
| `ERROR_BROWSER_BACKEND_BAD_RESPONSE` | 502 | The browser backend returned an invalid response. |

Example of an invalid API key:

```json
{
  "errorId": 1,
  "errorCode": "ERROR_WRONG_USER_KEY",
  "error": "API key is invalid."
}
```

HTTP status: `401 Unauthorized`.

Example of an unknown `method` value:

```json
{
  "errorId": 1,
  "errorCode": "ERROR_NO_SUCH_METHOD",
  "error": "Method is not supported."
}
```

HTTP status: `404 Not Found`.

# Limits and security

- Use a separate browser profile for each parallel process.
- Do not share the API key, `connectionUri`, browser password, or proxy password with third parties.
- Do not publish `connection.username`: the Base64URL segment embedded in it for the custom proxy may contain reversibly encoded proxy login and password.
- Keep in mind that the `connection` response returns the custom proxy along with its credentials; do not write the full JSON response to open logs.
- Do not store secrets in frontend code or public logs.
- When resending a purchase, use the same `idempotencyKey` only for the same order.
- Track your MegaIndex browser traffic balance and your provider's proxy traffic limit separately.

Maximum browser session duration: 30 minutes.

# Short FAQ

## What does MegaIndex bill for?

MegaIndex meters and sells browser traffic in GB. Additional traffic is purchased from the MegaIndex balance.

## Is proxy traffic included in browser traffic?

No. These are independent resources. MegaIndex does not meter or bill for your custom proxy traffic.

## Can I create a browser account without a proxy?

Yes. A proxy can be added later at the account or profile level, or passed when requesting `connectionUri`.

## Can I connect without my own proxy?

For initial testing, a profile with `proxyMode: "none"` may have access to a limited IPv6 fallback. Custom proxy is required for production connections.

## How much test browser traffic is provided?

A new user receives 1 GB of test browser traffic.

## Can I retry a purchase request after a network error?

Yes. For the same order, resend the same `idempotencyKey` to avoid a double charge.

## Is automatic CAPTCHA solving supported?

Yes. MegaIndex supports the same CDP `Captcha` interface as the 2Captcha Browser API.

## Do I need to pay separately for CAPTCHA solving?

No. CAPTCHA solving is included in Browser API. MegaIndex meters and charges only for browser traffic.
