SurSolver

BlogMTCaptcha Solver API: websiteKey, pageAction, token

MTCaptcha Solver API: websiteKey, pageAction, token

· Updated · 7 min read

MTCaptcha is easy to integrate once you know which values the widget was configured with. By the end of this post you will be able to build a correct createTask body, read the verified token from solution.token, and put it into the page before it expires.

What MTCaptcha checks

MTCaptcha runs risk analysis in the background first: fingerprints, behavioural signals and risk scores. Only visitors it finds suspicious get a visible text challenge. The vendor also sells invisible modes, called Low Friction and IP whitelist, where most people never see a widget at all.

What the site receives is a verified token shaped like v1(checksum,customerChecksum,Sitekey,randomSeed,encryptedTokenInfo). The site's server validates it with its secret private key through the vendor's token-check call. A token works once and stays valid for a few minutes. Your solver result is exactly that token, so everything below is about requesting it with the right inputs and delivering it in time.

For recognising the widget and the basics, start with the MTCaptcha solver guide. This post goes deeper into the parameters and the token's lifecycle.

The three parameters you need

Field Required What it is
websiteURL yes The exact URL of the page that hosts the widget
websiteKey yes The public sitekey, starting with MTPublic-
pageAction only if the site sets one The act value the widget was initialised with

Two optional fields exist besides the proxy settings: isInvisible and userAgent.

websiteKey is the sk query value of the getchallenge.json request to service.mtcaptcha.com. You can also read it in the console with window.mtcaptchaConfig.sitekey, or from mtcaptchaConfig in the page source.

pageAction is the act value of the same request, or the action entry of mtcaptchaConfig, for example login. When the request shows act=%24, the site uses the default action and you can leave pageAction out. When it shows a custom value, send exactly that. A token issued without the site's custom action carries a mismatched action, and the site's check should reject it.

Set isInvisible: true when the widget has no visible checkbox and works through a hidden field only. Invisible widgets escalate to a visible check when they become suspicious, and marking the mode helps avoid mis-solves.

The DevTools Network panel on the 2captcha.com MTCaptcha demo page, the getchallenge.json request selected with its Payload tab showing the sk and act query parameters

Use the same task body for a first test against the vendor's public demo page, which has a published demo key:

snippet.json
{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "MtCaptchaTaskProxyLess",
    "websiteURL": "https://service.mtcaptcha.com/mtcv1/demo/index.html",
    "websiteKey": "MTPublic-DemoKey9M"
  }
}

Real sites use their own key. Do not reuse the demo key anywhere else.

Proxyless or your own proxy

MtCaptchaTaskProxyLess needs no proxy details and is the default choice. Use MtCaptchaTask when the site rejects tokens solved from provider addresses, or when you want the solve to come from the same exit IP that will submit the form. MtCaptchaToken is also accepted as an alternative name for the same service.

MtCaptchaTask requires proxy details. Without them the request is rejected with ERROR_INVALID_REQUEST. Send task.proxy as a string, for example socks5://user:password@203.0.113.10:1080. A four-part string is always read as user:password:host:port. The split fields proxyType, proxyAddress, proxyPort, proxyLogin and proxyPassword also work, as described in the proxies guide.

The reason to care about the exit IP: MTCaptcha ties a token to a hostname and IP, and the token-check response reports both as tokeninfo.hostname and tokeninfo.ip. Whether a given site compares them is the site owner's choice, so treat an IP mismatch as one suspect when a correct-looking token is refused.

Read solution.token

Create the task, then poll getTaskResult every 3 seconds. While the solver works, the answer is {"errorId":0,"status":"processing"}. When it is done:

snippet.json
{
  "errorId": 0,
  "status": "ready",
  "solution": {
    "token": "v1(00cc43a5,1adfe4b4,MTPublic-DemoKey9M,0080ab49…)"
  }
}

solution.token is the verified token, ready to use as it is. A userAgent key can also appear in the solution. It is conditional, so check for it rather than assuming it is there; when it is present, send the request that submits the token with that same user agent.

API errors arrive as HTTP 200 with errorId: 1, an errorCode and an errorDescription. For example, a missing sitekey gives ERROR_INVALID_REQUEST and the description names the field. An unexpected server fault is an HTTP 500 without errorId. Treat both as failures and stop polling; a failed task is final and a retry is a new task.

solve.py
import time

import requests

API = 'https://api.sursolver.com'
CLIENT_KEY = 'YOUR_API_KEY'
POLL_SECONDS = 3
POLL_ATTEMPTS = 60


def call(path, body):
    response = requests.post(f'{API}{path}', json=body, timeout=30)
    response.raise_for_status()
    data = response.json()
    if data.get('errorId'):
        raise RuntimeError(f"{data['errorCode']}: {data['errorDescription']}")
    return data


def solve_mtcaptcha(page_url, sitekey, page_action=None):
    task = {
        'type': 'MtCaptchaTaskProxyLess',
        'websiteURL': page_url,
        'websiteKey': sitekey,
    }
    if page_action:
        task['pageAction'] = page_action
    created = call('/createTask', {'clientKey': CLIENT_KEY, 'task': task})
    for _ in range(POLL_ATTEMPTS):
        result = call(
            '/getTaskResult',
            {'clientKey': CLIENT_KEY, 'taskId': created['taskId']},
        )
        if result['status'] == 'ready':
            return result['solution']['token']
        time.sleep(POLL_SECONDS)
    raise TimeoutError(f"task {created['taskId']} was not ready in {POLL_ATTEMPTS} polls")

Inject the verified token

The widget keeps its answer in a hidden form field named mtcaptcha-verifiedtoken, often with an id such as mtcaptcha-verifiedtoken-1. Write the token there and submit the form. The site's own server then checks it.

Order matters because the token is short-lived. Fill in the rest of the form first, request the token last, and submit as soon as it arrives:

solve.py
from playwright.sync_api import sync_playwright

PAGE_URL = 'https://example.com/login'
SITEKEY = 'MTPublic-abCDEFJAB'
PAGE_ACTION = 'login'  # None when the request shows act=%24

with sync_playwright() as playwright:
    browser = playwright.chromium.launch()
    page = browser.new_page()
    page.goto(PAGE_URL)
    page.fill('input[name="email"]', 'user@example.com')
    page.fill('input[name="password"]', 'correct-horse-battery')

    token = solve_mtcaptcha(PAGE_URL, SITEKEY, PAGE_ACTION)
    page.eval_on_selector(
        'input[name="mtcaptcha-verifiedtoken"]',
        '(el, value) => { el.value = value; }',
        token,
    )
    page.click('button[type="submit"]')
    page.wait_for_load_state()
    browser.close()

The selectors for the other fields are placeholders; use the ones from your target. If the page reads the token through script instead of the hidden field, the equivalents are mtcaptcha.getVerifiedToken() and the status.verifiedToken value that the widget passes to its verified-callback. See how to submit a captcha token for the general patterns, and the Playwright recipe for the browser setup.

Token lifetime

A token is single-use. It stays valid for a few minutes, and the widget guarantees at least about 50 seconds of server-side validity. Do not solve ahead of time and queue tokens. Solve, submit, and for the next attempt request a new token. Never retry with the previous one.

Keep the vendor's token-check call on the server of the site that owns it. It needs the private key, which only the site owner has, and it must never run in a browser.

Why a token gets rejected

Cause What to check
Expired or reused token Submit right after ready; one token per attempt. The site owner sees fail_codes: ["token-expired"]
Wrong or missing pageAction Compare act in getchallenge.json with what you sent
Hostname mismatch websiteURL must be the page that hosts the widget, not another domain
IP mismatch Try MtCaptchaTask with the proxy you submit from
Invisible widget Add isInvisible: true
Field not filled Confirm the hidden mtcaptcha-verifiedtoken input exists when you write to it

The invisible flag and the IP mismatch are the least certain of these; the sources describe them as likely causes, not guaranteed ones.

Next steps

Create a task with MtCaptchaTaskProxyLess, your websiteURL, the MTPublic- key and, if the site sets one, pageAction. The accepted parameters and result keys are in the public catalog (GET /v1/task-types). A task is charged when createTask accepts it and failed tasks automatically return their original charge once, so confirm the sitekey and action before you scale up; the current price is on the pricing page. To see how the headers around the token matter, read browser headers and user agent for captcha solving.

More articles