API — Captcha solving

A separate listener speaking the anti-captcha.com protocol: point third-party software at it and solve images, access cookies, RCv2, CFt, GT and AW through your own proxies.

What this is

BlankTrail Proxy can accept captcha-solving tasks over the anti-captcha.com protocol. It is a separate listener on its own port: software that already speaks that protocol only needs to be pointed at your address instead of the solving service's.

The listener is separate rather than a set of routes on the dashboard port, for two reasons. Official protocol clients hit the host root, so they take the root paths entirely. And the captcha API may well be needed on the LAN where the dashboard must not be visible to anyone.

Off by defaultThe captcha API does not start until you enable it, and until then the port is not listened on at all. This is deliberate: a capability you do not use must not open a single port.

Turning it on

Three fields in the captcha_api section of the configuration file. The switch and the address can also be changed in the dashboard: Settings → the Captcha API card.

captcha_api:
  enabled: true
  addr: ":8892"
  allow_lan: false
  max_concurrent: 10
  task_ttl: "5m"
  port_idle_timeout: "10m"
FieldWhat it does
enabledWhether to start the listener. Off by default.
addrListener address. An empty host means loopback until LAN access is allowed.
allow_lanAccept requests from other machines even while the general Allow LAN is off. The captcha API follows that general dashboard setting, and this field opens it on top of it — separately from the proxy ports. On in the Docker image.
max_concurrentCap on concurrent tasks. Zero means no cap at all.
task_ttlHow long a finished task's answer is kept.
port_idle_timeoutHow long an idle port opened for a task's proxy is kept before closing.
Applies at onceThe switch and the address in the dashboard take effect immediately: the listener is raised or lowered on save, and an address already in use comes back as an error right in the dashboard. The Allow LAN switch takes effect the same way, without a restart. Editing the configuration file itself is still read at boot — restart the application for that.

In a container

Everything but the switch is already set up in the image: port 8892 is declared, the compose files shipped with it publish the port on 127.0.0.1, and the image configuration lets the listener bind beyond the loopback. All that is left is turning it on — with a variable or right in the dashboard. The override is one-way: variables turn things on and set values, but cannot switch off what the configuration has enabled.

docker run -d \
  -e BT_CAPTCHA_API_ENABLED=1 \
  -p 127.0.0.1:8892:8892 \
  ... blanktrail-proxy
Why allow_lan is on in the imageInside a container a request arrives from the Docker bridge address rather than from the loopback: with allow_lan off the bare host :8892 would bind to 127.0.0.1 and the published port would answer connection refused. BT_ALLOW_LAN turns on the general Allow LAN when the volume is created (together with the proxy password), and the captcha API follows it. BT_CAPTCHA_API_ALLOW_LAN opens the captcha API separately from the proxy ports and is only needed when you mount a configuration of your own instead of the image one.

Key and access

clientKey is your dashboard's API key — the very same one. There is no separate key for the captcha API: a second secret with its own storage and rotation is not worth introducing for a single surface.

  • The key travels in the request BODY, as the protocol requires, not in a header.
  • Key guessing is rate-limited: after a run of failures an address is refused regardless of whether the next key is correct.
  • Requests from other machines are refused until LAN access is allowed — by the general Allow LAN setting or by the allow_lan field — including getQueueStats and test.

Supported task types

TypeWhat it does
ImageToTextTaskReads the text on an image with a local model.
AntiGateTaskPasses the site's protection and returns the access cookies.
RecaptchaV2TaskSolves RCv2 from the page address and site key, through your proxy.
RecaptchaV2TaskProxylessThe same, but without your proxy — a direct exit.
TurnstileTaskGets a CFt token from the page address and site key, through your proxy.
TurnstileTaskProxylessThe same, without your proxy.
GeeTestTaskSolves GTv3 or GTv4 from the site's parameters, through your proxy.
GeeTestTaskProxylessThe same, without your proxy.
AmazonTaskPasses an AW wall and returns its token, through your proxy.
AmazonTaskProxylessThe same, without your proxy.

Other protocol types answer ERROR_TASK_NOT_SUPPORTED. That is a normal answer, not a failure: the client sees it right at task creation and can go to another service without waiting.

The Proxyless variants solve from the installation's own exit — the direct address of the machine BlankTrail Proxy runs on. If proxy fields are sent, such tasks do not use them.

How it works

  1. Create a task with createTask and get a numeric taskId.
  2. Poll getTaskResult while the answer says processing.
  3. Receive ready and the solution object — its contents depend on the task type.
Always HTTP 200Errors are reported by the errorId and errorCode fields in the body, not by the HTTP status — that is how the protocol works. A client seeing a non-200 would consider the service down and fall back to its own retries.

Exit and profile

Every task except ImageToTextTask is solved by the solver's browser, and it goes through a product port opened for the task's proxy. That is the same path as automatic solving on a proxy port. The browser's requests leave from your proxy's exit (for the Proxyless variants, from the installation's exit), with the TLS and HTTP/2 fingerprints and the user-agent of that port's profile. The token or cookies are obtained under the very identity the port presents to the site.

The port is opened for a proxy-plus-profile pair and lives until the idle time set by port_idle_timeout. Later tasks with the same proxy and the same profile go through the same port.

How the profile is chosen

The userAgent you send does not go on the wire. It SELECTS the nearest profile by browser family, major version and operating system, and what goes on the wire is that profile's user-agent — together with the TLS and HTTP/2 fingerprints that match it.

If userAgent is not sent or not recognised (Opera, Yandex Browser, Safari without a Version/ token), the port gets Chrome for Windows at the newest version in the profile database — the same as a port with a Chrome profile and no version set. AntiGateTask and AmazonTask have no userAgent field and always use this profile.

Use the userAgent from the answerIn the solution, the userAgent field contains the ACTUAL user-agent the token or cookies were obtained under. Put exactly that into your request. A foreign header on top of our wire is precisely the mismatch this product removes: returning it would make the captcha API a source of tells instead of hiding them.

Each such task is an attempt on the Challenge Breaker card of the dashboard, alongside the solves on proxy ports.

Reading an image

The image is passed as base64. The answer carries the recognised text in the text field.

{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "ImageToTextTask",
    "body": "iVBORw0KGgoAAAANSUhEUg..."
  }
}
The model is trained on its own setThe reader was trained on the RC vendor's text captchas. On captchas drawn differently the accuracy is noticeably lower, and that is a property of the model rather than a setting: measure the success rate on your own material before building a process on it.

Access cookies

AntiGateTask passes the site's protection and returns the cookies. Alongside them the answer carries the address of the port through which those cookies must be presented.

{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "AntiGateTask",
    "websiteURL": "https://example.com/",
    "proxyType": "http",
    "proxyAddress": "203.0.113.7",
    "proxyPort": 8080,
    "proxyLogin": "user",
    "proxyPassword": "pass"
  }
}
The cookies work only through the named portThe solution contains a fingerprint field with a blanktrail.replayProxy address. Access cookies are bound to the exit-plus-fingerprint pair, and presented from another address or another client they will be rejected by the site. Send your request through that port — it is already open for your proxy and lives until the idle time set by port_idle_timeout.

RCv2

The task needs the page address and the site's public key. The variant without your proxy is called RecaptchaV2TaskProxyless and requires no proxy fields.

{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "RecaptchaV2Task",
    "websiteURL": "https://example.com/login",
    "websiteKey": "6Lc_aCMTAAAAA...",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...",
    "proxyType": "http",
    "proxyAddress": "203.0.113.7",
    "proxyPort": 8080
  }
}

The answer is gRecaptchaResponse and userAgent (see the getTaskResult example below).

CFt

The task needs the page address and the site key (websiteKey, a string like 0x4AAAA…). The optional action, cData and chlPageData are passed to the widget as is. The variant without your proxy is TurnstileTaskProxyless.

{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "TurnstileTaskProxyless",
    "websiteURL": "https://example.com/login",
    "websiteKey": "0x4AAAAAAA...",
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."
  }
}

The answer is token and userAgent. The token goes into the form's cf-turnstile-response field or to the widget's callback; send the request carrying it with the userAgent from the answer.

{
  "errorId": 0,
  "status": "ready",
  "solution": {
    "token": "0.Zk3bE8...",
    "userAgent": "Mozilla/5.0 ..."
  }
}
A CF challenge page is not a widgetIf the site shows the interstitial “Just a moment…” page and expects a cf_clearance cookie, it is the wall that has to be solved, not a widget: that is AntiGateTask, which returns cookies. TurnstileTask returns the token of a widget embedded in a page or form. If the widget issues no token, the task ends with ERROR_CAPTCHA_UNSOLVABLE within a minute and a half.

GT

GTv3 and GTv4 are supported; the version field sets the version (3 or 4, 3 by default). The variant without your proxy is GeeTestTaskProxyless.

  • v3: needs the site's gt and challenge. The challenge is single-use — get it from the site right before creating the task. The optional geetestApiServerSubdomain sets the API server if the site does not use GT's default.
  • v4: needs the captchaId — in the gt field or in initParameters.captcha_id. The other initParameters are passed to initGeetest4 as is.
{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "GeeTestTaskProxyless",
    "websiteURL": "https://example.com/login",
    "gt": "0f1e2d3c4b5a69788796a5b4c3d2e1f0",
    "version": 4,
    "initParameters": {"riskType": "slide"},
    "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."
  }
}

The v3 answer is challenge, validate and seccode; the v4 answer is captcha_id, lot_number, pass_token, gen_time and captcha_output. userAgent comes with them. Your request sends these fields to the site, and the site verifies them with GT.

{
  "errorId": 0,
  "status": "ready",
  "solution": {
    "captcha_id": "0f1e2d3c4b5a69788796a5b4c3d2e1f0",
    "lot_number": "7c1b2a...",
    "pass_token": "a8d9...",
    "gen_time": "1790770800",
    "captcha_output": "Qx3k...",
    "userAgent": "Mozilla/5.0 ..."
  }
}
The slider is what gets solvedThe solver passes the slider challenge — dragging the puzzle piece. On other kinds of GT challenge the task ends with ERROR_CAPTCHA_UNSOLVABLE.

AW

The task needs the address of a page where the site shows an AW wall. The websiteKey, iv, context, challengeScript and captchaScript fields are accepted for compatibility but not needed: the solver takes all of that from the page itself. The variant without your proxy is AmazonTaskProxyless.

{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "AmazonTask",
    "websiteURL": "https://example.com/",
    "proxyType": "http",
    "proxyAddress": "203.0.113.7",
    "proxyPort": 8080,
    "proxyLogin": "user",
    "proxyPassword": "pass"
  }
}

The answer is token and userAgent. token is the value of the aws-waf-token cookie: set it in your request under that name and send the request with the userAgent from the answer. The ip field of the getTaskResult answer is the exit address the token was obtained from.

{
  "errorId": 0,
  "status": "ready",
  "solution": {
    "token": "a1b2c3d4-...:AAABk...",
    "userAgent": "Mozilla/5.0 ..."
  },
  "ip": "203.0.113.7"
}
A token exists only where there was a wallThe solver passes the wall the site showed on this exit. If there was no wall, there is no cookie, and the task ends with ERROR_CAPTCHA_UNSOLVABLE. An AWS captcha inside a form (wafType: widget) is not a wall: it appears on a user's action, so such a task gets ERROR_TASK_NOT_SUPPORTED at once.

Methods

POST/createTaskclientKey required

Creates a task and returns a numeric taskId.

Response
{
  "errorId": 0,
  "taskId": 12
}
POST/getTaskResultclientKey required

Returns the task's state, and the solution object once it is ready.

Request
{
  "clientKey": "YOUR_API_KEY",
  "taskId": 12
}
Response
{
  "errorId": 0,
  "status": "ready",
  "solution": {
    "gRecaptchaResponse": "03AGdBq26...",
    "userAgent": "Mozilla/5.0 ..."
  }
}
POST/getBalanceclientKey required

Returns the balance. The product has no billing, so the value is constant; zero means the licence is inactive.

POST/getQueueStatsno key required

Load: how many tasks are running and how busy the solver pool is.

POST/reportIncorrectImageCaptchaclientKey required

Accepted for compatibility: the product cannot learn from reports.

POST/reportIncorrectRecaptchaclientKey required

The same for RC.

POST/reportCorrectRecaptchaclientKey required

The same, for confirming a correct solution.

POST/testno key required

Returns the parsed request body. Useful when a third-party client sends the wrong thing.

Error codes

The codes and their names are the protocol's: client libraries compare the errorCode string itself. Below are the ones this service returns.

errorIderrorCodeWhen
1ERROR_KEY_DOES_NOT_EXISTThe key is wrong, or the address is temporarily blocked for guessing.
2ERROR_NO_SLOT_AVAILABLENo free solvers right now, or the max_concurrent cap is reached.
10ERROR_ZERO_BALANCEThe licence is inactive: renew it, tasks are not accepted.
11ERROR_IP_NOT_ALLOWEDThe request came from another machine while LAN access is not allowed — neither by the general Allow LAN nor by allow_lan.
12ERROR_CAPTCHA_UNSOLVABLESolving failed. You may retry, but the profile stays the same until the port closes on idle; if the failure repeats, check the page address and the site parameters: the key, gt, challenge.
16ERROR_NO_SUCH_CAPCHA_IDNo task with that taskId: it finished and was dropped once task_ttl elapsed, or it belongs to another key.
23ERROR_TASK_NOT_SUPPORTEDThe task type is not supported, the required model is not installed, or the task is of the wrong kind — for example an AWS captcha inside a form (wafType: widget).
25ERROR_PROXY_CONNECT_REFUSEDYour proxy refused the connection. Fix the proxy; retrying the task is pointless.
26ERROR_PROXY_CONNECT_TIMEOUTYour proxy could not be reached within the allotted time.
27ERROR_PROXY_READ_TIMEOUTYour proxy accepted the connection and went silent.
49ERROR_PROXY_NOT_AUTHORISEDYour proxy rejected the login or the password.
The proxy codes name YOUR proxyThe four codes 25, 26, 27 and 49 refer to the proxy you sent in the task, not to our exit. If your proxy answers correctly but solving failed, you will get ERROR_CAPTCHA_UNSOLVABLE: we do not blame your proxy for our own failure.

Limits

  • Tasks live in memory and do not survive a restart: afterwards the old taskId answers ERROR_NO_SUCH_CAPCHA_ID.
  • A task that does not finish within its budget is declared failed and frees its place in the cap.
  • There is no callback: the callbackUrl field is accepted and ignored, the result is fetched by polling.
  • Tasks for the same site through the same proxy run one after another — a property of the anti-replay design, not a configuration cap.

Where next