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
Captchainterface; - 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:
- browser traffic on MegaIndex;
- 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¤cy=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.beforeandbalance.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: trueconfirms the operation was already processed;financeIdmatches the ID of the original purchase;- no repeat charge occurs and no additional browser traffic is credited;
- instead of
balance.before/balance.after,balance.currentis returned; - the
trafficblock 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 requestingconnectionUriand in the CDP URL;profilesCountandmaxProfiles— the current and maximum number of profiles in the account;usedKb,usedGb, andrequestCount— 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.proxyModeisnone;account.customProxyisnull;profile.isDefaultistrue;profile.nameisDefault profile;profile.proxyModeisinherit;account.defaultProfileIdmatches the numericprofile.id;- statistics for the new account/profile start at zero;
profile.lastUsedAtis 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
idand a stringprofileId; - has
isDefault: false; - uses
proxyMode: "inherit"by default; - starts with zero
usedKb,usedGb, andrequestCount; - has
source: "frontend"; - has an empty
lastUsedAtuntil 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:
- custom proxy passed for a specific
connectionUri; - custom proxy saved on the browser profile;
- custom proxy saved on the browser account;
- 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
customProxyobject, 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:
- Automatic solving after the page loads.
- Manual triggering via the
Captcha.solvecommand.
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.
Recommended scenarios
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
- Obtain
connectionUriviamethod=connectionrather than assembling the WebSocket URL manually. - Always pass a custom proxy for production scenarios, either via the connection request or via saved account/profile settings.
- Use separate profiles for stable isolation across different scenarios.
- If you only need confirmation that the CAPTCHA was passed, use automatic mode and events.
- If you need the token, use manual
Captcha.solve. - Increase the CDP client timeout for manual solving.
- Check
result.status, not just whether the command returned a response. - 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
connectionresponse 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
idempotencyKeyonly 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.