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:

npm install playwright

Save the CDP URL to an environment variable:

export MEGAINDEX_BROWSER_CDP_URL="PASTE_CDP_URL_HERE"

Create a quick-start.js file:

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:

node quick-start.js

4. Connect via Puppeteer

Install Puppeteer Core:

npm install puppeteer-core
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:

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

The examples below use this variable:

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:

/profile/api-key

After reissuing, the old key stops working.

The key is supported in four formats.

Query string:

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

JSON body:

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

X-API-Key header:

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

Bearer token:

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:

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

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

With an explicit currency:

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

The response contains an array of price tiers:

[
  {
    "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

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:

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

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

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

Abridged example response:

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

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:

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

Successful response:

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

Browser profiles

Get the list of profiles

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:

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

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

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

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

For a specific profile:

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

Connecting with a proxy for the current session

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:

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

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

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

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:

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

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

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:

No active frame

Captcha.solve

Triggers an explicit CAPTCHA solve on the current tab:

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:

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

CAPTCHA not found:

{
  "result": {
    "status": "notDetected"
  }
}

Possible CDP errors without a SolveResult:

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

CDP events

Subscribing in Playwright:

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:

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.

Automatic CAPTCHA solving

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

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:

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:

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:

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

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

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

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

The method returns the browser traffic purchase history:

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

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

Abridged example response:

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

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

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

HTTP status: 401 Unauthorized.

Example of an unknown method value:

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