capskip 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.2.0] - 2026-09-12
9
+
10
+ ### Added
11
+
12
+ - **ALTCHA support** via `altcha(url, options)`. Pass `challengeUrl` for CapSkip
13
+ to fetch the challenge, or `challengeJson` with the document itself (a JSON
14
+ string, or an object which is serialized for you). Sending both is allowed —
15
+ the inline document wins. The result exposes `token` (the value the site's
16
+ `altcha` form field expects) and `number`, the counter that solved it; `code`
17
+ keeps the same raw string.
18
+ - Parameter aliases `challengeUrl`/`challengeURL` for `challenge_url` and
19
+ `challengeJson`/`challengeJSON` for `challenge_json`.
20
+ - `AltchaOptions` and the `token`/`number` result fields in the TypeScript
21
+ definitions.
22
+ - Both ALTCHA generations are handled: the legacy scheme (SHA-1/256/384/512) and
23
+ proof-of-work v2 (PBKDF2 or SHA). Their tokens are shaped differently — a v2
24
+ payload carries no top-level `number`, its counter sitting at
25
+ `solution.counter` — so the counter is taken from the server's own `solution`
26
+ object, the one field both report the same way, and dug out of the token only
27
+ when a poll did not carry it.
28
+
29
+ ### Notes
30
+
31
+ - ALTCHA is CPU proof-of-work rather than a browser solve, so it uses
32
+ `defaultTimeout` instead of the longer `recaptchaTimeout` that reCAPTCHA,
33
+ Turnstile and GeeTest use.
34
+ - A proxy passed to `altcha()` applies only to the `challengeUrl` fetch; a task
35
+ carrying its challenge inline never touches the network.
36
+
37
+ ## [1.1.0] - 2026-07-26
38
+
39
+ ### Added
40
+
41
+ - **GeeTest v3 (slide) support** via `geetest(gt, challenge, url, options)`.
42
+ Accepts the optional `api_server` domain override and the usual `proxy`
43
+ option. The result exposes the answer as the parsed `challenge`, `validate`,
44
+ and `seccode` fields, while `code` keeps the raw JSON string CapSkip returns.
45
+ - Parameter aliases `apiServer` and `api_subdomain` for `api_server`.
46
+ - TypeScript definitions for `geetest()`, `GeetestOptions`, and the new
47
+ `SolveResult` fields.
48
+ - `proxytype` is now validated against the values CapSkip accepts (`HTTP`,
49
+ `HTTPS`, `SOCKS5`, `SOCKS5H`, case-insensitive) for every proxy-capable captcha
50
+ type. `SOCKS4` and other values previously reached the server and came back as
51
+ `ERROR_BAD_PARAMETERS`; they now raise `ValidationException` locally.
52
+
8
53
  ## [1.0.2] - 2026-07-15
9
54
 
10
55
  ### Fixed
package/README.md CHANGED
@@ -1,20 +1,52 @@
1
- # CapSkip Node.js SDK
1
+ # CapSkip Node.js SDK — Captcha Solver for Node.js
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/capskip.svg)](https://www.npmjs.com/package/capskip)
3
4
  [![Node.js 18+](https://img.shields.io/badge/node-18%2B-brightgreen.svg)](https://nodejs.org/)
4
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
5
6
  [![Tests](https://github.com/capskip/capskip-node/actions/workflows/ci.yml/badge.svg)](https://github.com/capskip/capskip-node/actions/workflows/ci.yml)
6
7
 
7
- Official Node.js client for the [CapSkip](https://capskip.com) **local** captcha solver.
8
+ **Solve reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, GeeTest, ALTCHA and image captchas from Node.js.**
8
9
 
9
- CapSkip runs on your machine and exposes a standard captcha-solver HTTP API (the familiar `in.php` / `res.php` endpoints). This SDK wraps that API with clean, familiar method names, so you can solve captchas locally — no cloud service and no per-solve API fees beyond your CapSkip license.
10
+ Official Node.js client for [CapSkip](https://capskip.com), a **local captcha solver** that runs on your own machine. Licensed once, not billed per solve. TypeScript definitions included.
11
+
12
+ ```bash
13
+ npm install capskip
14
+ ```
15
+
16
+ ---
17
+
18
+ ## How it works
19
+
20
+ CapSkip is a desktop app. It does the solving on your machine and exposes the standard captcha-solver HTTP API — the same `in.php` / `res.php` endpoints every 2captcha-compatible client already speaks — on `127.0.0.1:8080`.
21
+
22
+ This SDK is a thin wrapper over that API, with the method names you would expect: `normal()`, `recaptcha()`, `turnstile()`, `geetest()`, `altcha()`. Nothing leaves your network, and there is no credit balance to keep an eye on.
23
+
24
+ ## Supported captcha types
25
+
26
+ | Captcha | Method |
27
+ |---|---|
28
+ | **Image captcha solver** (distorted text / OCR) | `solver.normal(file)` |
29
+ | **reCAPTCHA v2 solver** (checkbox) | `solver.recaptcha(sitekey, url)` |
30
+ | **reCAPTCHA v2 invisible solver** | `solver.recaptcha(sitekey, url, { invisible: 1 })` |
31
+ | **reCAPTCHA Enterprise solver** | `solver.recaptcha(sitekey, url, { enterprise: 1 })` |
32
+ | **reCAPTCHA v3 solver** | `solver.recaptcha(sitekey, url, { version: 'v3', action: 'submit' })` |
33
+ | reCAPTCHA v3 Enterprise | `solver.recaptcha(sitekey, url, { version: 'v3', enterprise: 1 })` |
34
+ | **Cloudflare Turnstile solver** (widget) | `solver.turnstile(sitekey, url)` |
35
+ | Cloudflare Turnstile (challenge page) | `solver.turnstile(sitekey, url, { data, pagedata })` |
36
+ | **GeeTest v3 solver** (slide puzzle) | `solver.geetest(gt, challenge, url)` |
37
+ | **ALTCHA solver** (proof-of-work) | `solver.altcha(url, { challengeUrl })` |
38
+
39
+ **hCaptcha and FunCaptcha/Arkose are not supported.** hCaptcha is the one people misidentify most often, since it also puts a `data-sitekey` on the widget — check for `class="h-captcha"` or a `js.hcaptcha.com` script before reaching for `recaptcha()`.
40
+
41
+ Point the SDK at the [live captcha demo pages](https://capskip.com/captcha-demo/) to sanity-check your setup against real widgets.
10
42
 
11
43
  ---
12
44
 
13
45
  ## Quick start (5 minutes)
14
46
 
15
- ### 1. Install CapSkip
47
+ ### 1. Install the CapSkip captcha solver
16
48
 
17
- Download and run the CapSkip desktop app from [capskip.com](https://capskip.com). Leave it running in the background.
49
+ Download and run the CapSkip desktop app — [CapSkipInstaller.msi](https://capskip.com/download/CapSkipInstaller.msi). Leave it running in the background.
18
50
 
19
51
  In CapSkip settings, note:
20
52
 
@@ -58,18 +90,20 @@ Every solve method returns a Promise — use `await` or `.then()`.
58
90
 
59
91
  ---
60
92
 
61
- ## Supported captcha types
93
+ ## Why solve captchas locally
62
94
 
63
- | Type | SDK method |
64
- |---|---|
65
- | Image CAPTCHA (distorted text) | `solver.normal(file)` |
66
- | reCAPTCHA v2 (checkbox) | `solver.recaptcha(sitekey, url)` |
67
- | reCAPTCHA v2 Invisible | `solver.recaptcha(sitekey, url, { invisible: 1 })` |
68
- | reCAPTCHA v2 Enterprise | `solver.recaptcha(sitekey, url, { enterprise: 1 })` |
69
- | reCAPTCHA v3 | `solver.recaptcha(sitekey, url, { version: 'v3' })` |
70
- | reCAPTCHA v3 Enterprise | `solver.recaptcha(sitekey, url, { version: 'v3', enterprise: 1 })` |
71
- | Cloudflare Turnstile (widget) | `solver.turnstile(sitekey, url)` |
72
- | Cloudflare Turnstile (challenge page) | `solver.turnstile(sitekey, url, { data, pagedata })` |
95
+ Cloud captcha APIs charge per solve, which turns a retry loop into an expense and routes every page URL and sitekey you touch through someone else's queue.
96
+
97
+ CapSkip flips that around:
98
+
99
+ - **Unlimited solving** — one license, no per-captcha charge, no balance to top up
100
+ - **Runs on `127.0.0.1`** — the SDK never talks to a third-party server
101
+ - **No per-key rate limit** — throughput is whatever your machine can manage
102
+ - **Fast** — image captchas come back in well under a second; a typical reCAPTCHA v2 lands in 30–45 seconds
103
+
104
+ ### Coming from 2captcha or Anti-Captcha
105
+
106
+ CapSkip answers on the same `in.php` / `res.php` endpoints, so it works as a **2captcha API alternative**: an existing integration usually needs nothing more than its host pointed at `127.0.0.1:8080`. The [migration notes](https://capskip.com/2captcha-api-alternative/) cover the details, if you would rather keep your current client library than switch to this one.
73
107
 
74
108
  ---
75
109
 
@@ -97,7 +131,8 @@ const solver = new CapSkip({
97
131
  host: '127.0.0.1', // CapSkip host
98
132
  port: 8080, // CapSkip port from app settings
99
133
  defaultTimeout: 120, // seconds — image captcha polling timeout
100
- recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile polling timeout
134
+ recaptchaTimeout: 300, // seconds — reCAPTCHA / Turnstile / GeeTest polling timeout
135
+ // (ALTCHA uses defaultTimeout — CPU work, not a browser solve)
101
136
  pollingInterval: 5, // max seconds between res.php polls (starts at 0.25s, backs off to this)
102
137
  });
103
138
  ```
@@ -161,7 +196,41 @@ const v3 = await solver.recaptcha('...', 'https://example.com', {
161
196
  const result = await solver.turnstile('0x4AAAAAAA...', 'https://example.com');
162
197
  ```
163
198
 
164
- ### With a proxy (reCAPTCHA & Turnstile only)
199
+ ### GeeTest v3
200
+
201
+ `gt` is static per site, but `challenge` is single-use and expires in about a
202
+ minute — fetch a fresh pair right before solving.
203
+
204
+ ```js
205
+ const result = await solver.geetest(
206
+ '81388ea1fc187e0c335c0a8907ff2625',
207
+ '7cf6a8b1a2c34d5e6f7089abcdef0123',
208
+ 'https://example.com/login',
209
+ );
210
+
211
+ // Post these back exactly as the site's own front-end would
212
+ result.challenge, result.validate, result.seccode;
213
+ ```
214
+
215
+ ### ALTCHA
216
+
217
+ ALTCHA is proof-of-work, not recognition — there is nothing to read, so a solve
218
+ is deterministic and takes milliseconds. Give CapSkip the endpoint that serves
219
+ the challenge, or the challenge document itself.
220
+
221
+ ```js
222
+ const result = await solver.altcha('https://example.com/signup', {
223
+ challengeUrl: 'https://example.com/captcha/api/altcha/challenge',
224
+ });
225
+
226
+ // Post this back in the form field the widget uses, named `altcha`
227
+ result.token;
228
+ ```
229
+
230
+ Challenges expire fast — some sites inside two minutes — so fetch one
231
+ immediately before solving and submit the token promptly.
232
+
233
+ ### With a proxy (reCAPTCHA, Turnstile, GeeTest & ALTCHA only)
165
234
 
166
235
  ```js
167
236
  // Proxy is not supported for image captcha
@@ -196,6 +265,53 @@ More examples: [`examples/`](examples/)
196
265
 
197
266
  ---
198
267
 
268
+ ## Puppeteer, Playwright and Selenium
269
+
270
+ The SDK hands back a token; your existing browser tooling does the driving. The shape is the same whichever you use:
271
+
272
+ 1. Read the sitekey off the page (`data-sitekey`, or the widget's config object).
273
+ 2. Call the matching solve method with that sitekey and the page URL.
274
+ 3. Write the token into the response field and submit.
275
+
276
+ ### Puppeteer
277
+
278
+ ```js
279
+ const sitekey = await page.$eval('[data-sitekey]', el => el.dataset.sitekey);
280
+ const { code } = await solver.recaptcha(sitekey, page.url());
281
+
282
+ await page.evaluate(t => {
283
+ document.getElementById('g-recaptcha-response').value = t;
284
+ }, code);
285
+ await page.click('button[type=submit]');
286
+ ```
287
+
288
+ ### Playwright
289
+
290
+ ```js
291
+ const sitekey = await page.getAttribute('[data-sitekey]', 'data-sitekey');
292
+ const { code } = await solver.recaptcha(sitekey, page.url());
293
+
294
+ await page.evaluate(t => {
295
+ document.getElementById('g-recaptcha-response').value = t;
296
+ }, code);
297
+ ```
298
+
299
+ ### Selenium WebDriver
300
+
301
+ ```js
302
+ const el = await driver.findElement(By.css('[data-sitekey]'));
303
+ const sitekey = await el.getAttribute('data-sitekey');
304
+ const { code } = await solver.recaptcha(sitekey, await driver.getCurrentUrl());
305
+
306
+ await driver.executeScript(
307
+ 'document.getElementById("g-recaptcha-response").value = arguments[0];', code,
308
+ );
309
+ ```
310
+
311
+ Longer walkthroughs: [Puppeteer](https://capskip.com/puppeteer-captcha-solver/), [Playwright](https://capskip.com/playwright-captcha-solver/) and [Selenium](https://capskip.com/selenium-captcha-solver/).
312
+
313
+ ---
314
+
199
315
  ## Return value
200
316
 
201
317
  Every solve method resolves to:
@@ -208,6 +324,10 @@ Every solve method resolves to:
208
324
  }
209
325
  ```
210
326
 
327
+ GeeTest additionally expands its answer into `challenge`, `validate`, and
328
+ `seccode`, while `code` keeps the raw JSON string. ALTCHA adds `token` (the same
329
+ string as `code`) and `number`, the counter that solved it.
330
+
211
331
  ---
212
332
 
213
333
  ## Error handling
@@ -247,6 +367,42 @@ const result: SolveResult = await solver.recaptcha('...', 'https://example.com')
247
367
 
248
368
  ---
249
369
 
370
+ ## FAQ
371
+
372
+ ### How do I solve a captcha in Node.js?
373
+
374
+ Install the CapSkip desktop app, `npm install capskip`, then call the method that matches the widget — `recaptcha()`, `turnstile()`, `geetest()`, `altcha()` or `normal()`. Each returns a Promise that resolves once CapSkip has an answer, giving you a token, or the recognized text in the case of an image captcha.
375
+
376
+ ### Is this a free captcha solver?
377
+
378
+ The SDK itself is MIT-licensed and free. Solving needs the CapSkip app, which is bought once rather than metered per captcha, so your cost stops scaling with volume.
379
+
380
+ ### Which captchas can it solve?
381
+
382
+ reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, and image/text captchas. Not hCaptcha, and not FunCaptcha/Arkose.
383
+
384
+ ### Does it work with Puppeteer and Playwright?
385
+
386
+ Yes — see [above](#puppeteer-playwright-and-selenium). The SDK never touches a browser itself, so it drops into whatever stack you already have, Selenium and plain `fetch` included.
387
+
388
+ ### Why is my reCAPTCHA v3 score low?
389
+
390
+ Google derives v3 scores from IP reputation, cookies and browsing history. A solver returns a valid token, but it cannot change how Google grades that token — `score` is forwarded as the target you want, not a guarantee. If a site enforces a high threshold, solve through a cleaner IP using the `proxy` option.
391
+
392
+ ### Can I use it as a 2captcha alternative?
393
+
394
+ Yes. CapSkip serves the same endpoints, so you can either move to this SDK or repoint an existing 2captcha client at `127.0.0.1:8080`.
395
+
396
+ ### Does the captcha have to be on a public page?
397
+
398
+ For widget captchas, yes — CapSkip loads the URL you pass it. Image captchas only need the image, and that can be a local file.
399
+
400
+ ### Does it work with TypeScript and ESM?
401
+
402
+ Types ship in the package. The runtime is CommonJS, so `import { CapSkip } from 'capskip'` works from ESM through Node's interop.
403
+
404
+ ---
405
+
250
406
  ## Development
251
407
 
252
408
  ```bash
@@ -262,10 +418,12 @@ The test suite uses Node's built-in test runner — no extra dependencies. See [
262
418
 
263
419
  ## Links
264
420
 
265
- - [CapSkip website](https://capskip.com)
266
- - [CapSkip API docs](https://capskip.com/api-docs/)
267
- - [Report an issue](https://github.com/capskip/capskip-node/issues)
268
- - [npm package](https://www.npmjs.com/package/capskip)
421
+ - [CapSkip — local captcha solver](https://capskip.com) · [download the app](https://capskip.com/download/CapSkipInstaller.msi)
422
+ - [Captcha demo pages](https://capskip.com/captcha-demo/) — live reCAPTCHA, Turnstile, GeeTest and image widgets
423
+ - [Node.js captcha solver guide](https://capskip.com/nodejs-captcha-solver/)
424
+ - [HTTP API docs](https://capskip.com/api-docs/)
425
+ - Other clients: [Python](https://github.com/capskip/capskip-python) · [PHP](https://github.com/capskip/capskip-php) · [.NET](https://github.com/capskip/capskip-dotnet) · [MCP server for AI agents](https://github.com/capskip/capskip-mcp)
426
+ - [npm package](https://www.npmjs.com/package/capskip) · [report an issue](https://github.com/capskip/capskip-node/issues)
269
427
 
270
428
  ---
271
429
 
@@ -22,8 +22,11 @@ method returns a `Promise`.
22
22
  | reCAPTCHA v2 | `recaptcha(sitekey, url)` | `userrecaptcha` |
23
23
  | reCAPTCHA v3 | `recaptcha(sitekey, url, { version: 'v3' })` | `userrecaptcha` + `version=v3` |
24
24
  | Cloudflare Turnstile | `turnstile()` | `turnstile` |
25
+ | GeeTest v3 (slide) | `geetest()` | `geetest` |
26
+ | ALTCHA (proof-of-work) | `altcha()` | `altcha` |
25
27
 
26
- **Proxy** is supported for reCAPTCHA and Turnstile only — not for image captcha.
28
+ **Proxy** is supported for reCAPTCHA, Turnstile, GeeTest, and ALTCHA — not for image
29
+ captcha. For ALTCHA the proxy is used only for the `challengeUrl` fetch.
27
30
 
28
31
  ---
29
32
 
@@ -206,6 +209,174 @@ const challenge = await solver.turnstile('0x4AAAAAAA...', 'https://example.com',
206
209
 
207
210
  ---
208
211
 
212
+ ## 5. GeeTest v3 — `geetest(gt, challenge, url, { ... })`
213
+
214
+ ### POST `/in.php`
215
+
216
+ | Parameter | Type | Required | Description |
217
+ |---|---|---|---|
218
+ | `key` | string | Yes | CapSkip API key |
219
+ | `method` | string | Yes | `geetest` |
220
+ | `gt` | string | Yes | Static per-site GeeTest id |
221
+ | `challenge` | string | Yes | Single-use challenge token |
222
+ | `pageurl` | string | Yes | Full page URL |
223
+ | `api_server` | string | No | GeeTest API server domain, e.g. `api-na.geetest.com` |
224
+ | `json` | int | No | `0` plain text, `1` JSON |
225
+ | `proxy` | string | No | Proxy address |
226
+ | `proxytype` | string | No | Proxy type |
227
+
228
+ ### Getting `gt` and `challenge`
229
+
230
+ Both come from the target site, which fetches them from an endpoint returning
231
+ `{"gt": "...", "challenge": "..."}` (often `.../register.php` or a `gettype`/`get.php`
232
+ request). Find it in DevTools → Network, or read them out of the
233
+ `initGeetest({ gt, challenge })` call in the page scripts.
234
+
235
+ > **`challenge` is single-use and expires in about a minute.** Fetch a fresh pair
236
+ > immediately before each solve. If a solve comes back with a bad-challenge error,
237
+ > request a new pair and retry — reusing one never succeeds.
238
+
239
+ ### SDK usage
240
+
241
+ ```js
242
+ const result = await solver.geetest(
243
+ '81388ea1fc187e0c335c0a8907ff2625',
244
+ '7cf6a8b1a2c34d5e6f7089abcdef0123',
245
+ 'https://example.com/login',
246
+ );
247
+
248
+ result.challenge; // geetest_challenge
249
+ result.validate; // geetest_validate
250
+ result.seccode; // geetest_seccode
251
+ result.code; // the same answer as a raw JSON string
252
+ ```
253
+
254
+ Post the three fields back exactly as the site's own front-end would:
255
+
256
+ ```js
257
+ await fetch(LOGIN_URL, {
258
+ method: 'POST',
259
+ body: new URLSearchParams({
260
+ geetest_challenge: result.challenge,
261
+ geetest_validate: result.validate,
262
+ geetest_seccode: result.seccode,
263
+ }),
264
+ });
265
+ ```
266
+
267
+ GeeTest is a real browser solve, so it uses the longer `recaptchaTimeout` budget
268
+ rather than `defaultTimeout`.
269
+
270
+ ---
271
+
272
+ ## 6. ALTCHA — `altcha(url, { ... })`
273
+
274
+ ALTCHA is not a recognition captcha. There is no image, audio or text to read:
275
+ the site issues a proof-of-work challenge and the client must brute-force a
276
+ number that satisfies it. A solve is therefore deterministic and cheap —
277
+ typically milliseconds.
278
+
279
+ ### POST `/in.php`
280
+
281
+ | Parameter | Type | Required | Description |
282
+ |---|---|---|---|
283
+ | `key` | string | Yes | CapSkip API key |
284
+ | `method` | string | Yes | `altcha` |
285
+ | `pageurl` | string | Yes | Full URL of the page the challenge came from |
286
+ | `challenge_url` | string | One of the two | Endpoint CapSkip fetches the challenge from |
287
+ | `challenge_json` | string | One of the two | The challenge document itself, as a JSON string |
288
+ | `json` | int | No | `0` plain text, `1` JSON |
289
+ | `proxy` | string | No | Proxy address — used **only** for the `challenge_url` fetch |
290
+ | `proxytype` | string | No | Proxy type |
291
+
292
+ Sending both challenge parameters is allowed: the inline `challengeJson` wins,
293
+ because fetching would only re-obtain what you already supplied.
294
+
295
+ ### Getting the challenge
296
+
297
+ Open DevTools → Network on the target page and look for the request the
298
+ `<altcha-widget>` makes for its challenge (often something like
299
+ `/altcha/challenge`). The request URL is your `challengeUrl`; its JSON response
300
+ is your `challengeJson`.
301
+
302
+ The widget attribute that names that endpoint depends on the widget version, so
303
+ read the page source rather than assuming:
304
+
305
+ | Widget | Attribute |
306
+ |---|---|
307
+ | v1 / v2 | `challengeurl="…"`, with a separate `challengejson="…"` for an inline challenge |
308
+ | v3+ | `challenge="…"` — the same attribute takes either a URL or the challenge data |
309
+
310
+ > **Challenges expire, and the window is short** — some sites inside two minutes.
311
+ > Once expired, the site refuses the solution with a bare "verification failed"
312
+ > that looks exactly like a wrong answer. Fetch the challenge immediately before
313
+ > solving and submit the token promptly; do not fetch a batch in advance, and do
314
+ > not hold a token while a user fills in a form.
315
+ >
316
+ > CapSkip refuses an already-expired inline challenge immediately rather than
317
+ > burning CPU on a token that cannot work. If you passed `challengeUrl` and the
318
+ > challenge expired while the job queued, it fetches a fresh one automatically.
319
+
320
+ ### SDK usage
321
+
322
+ ```js
323
+ // CapSkip fetches the challenge for you
324
+ const result = await solver.altcha('https://example.com/signup', {
325
+ challengeUrl: 'https://example.com/captcha/api/altcha/challenge',
326
+ });
327
+
328
+ // …or hand it the document you already have. No network request at all.
329
+ const inline = await solver.altcha('https://example.com/signup', {
330
+ challengeJson: {
331
+ algorithm: 'SHA-256', challenge: '…', salt: '…',
332
+ signature: '…', maxnumber: 1000000,
333
+ },
334
+ });
335
+
336
+ result.token; // the base64 payload to post back
337
+ result.number; // the counter that solved it
338
+ result.code; // the same string as token
339
+ ```
340
+
341
+ `challengeJson` accepts an object (serialized for you) or a JSON string.
342
+
343
+ `number` is reported for both ALTCHA generations. Their tokens differ — a legacy
344
+ payload carries it as a top-level `number`, while a proof-of-work v2 payload has
345
+ none, its counter sitting at `solution.counter` — so it is read from the server's
346
+ own `solution` object, which reports both the same way.
347
+
348
+ Post the token back in the form field the widget uses, named `altcha`:
349
+
350
+ ```js
351
+ await fetch(SIGNUP_URL, {
352
+ method: 'POST',
353
+ body: new URLSearchParams({
354
+ email: 'someone@example.com',
355
+ altcha: result.token,
356
+ }),
357
+ });
358
+ ```
359
+
360
+ Do not re-encode, trim or re-order the token: it is base64 of a JSON document
361
+ whose fields are covered by the server's HMAC signature, so any modification
362
+ invalidates it. Some integrations read the payload from a JSON body field
363
+ instead — check what the page's own submit sends and mirror it.
364
+
365
+ Unlike GeeTest and reCAPTCHA this is CPU proof-of-work rather than a browser
366
+ solve, so it uses `defaultTimeout`, not `recaptchaTimeout`.
367
+
368
+ ### Algorithms
369
+
370
+ CapSkip supports the legacy scheme (SHA-1/256/384/512) and PoW v2 with PBKDF2 or
371
+ SHA. **Argon2id and scrypt are refused**, not attempted: a task using one returns
372
+ `ERROR_CAPTCHA_UNSOLVABLE` and is never retried. ALTCHA itself recommends PBKDF2
373
+ as the default, so this affects a minority of sites.
374
+
375
+ All three widget types (`native`, `checkbox`, `switch`) work — the distinction is
376
+ purely visual and never reaches CapSkip.
377
+
378
+ ---
379
+
209
380
  ## Return value
210
381
 
211
382
  Every solve method resolves to:
@@ -218,6 +389,31 @@ Every solve method resolves to:
218
389
  }
219
390
  ```
220
391
 
392
+ ALTCHA additionally exposes `token` (the same string as `code`, named for the
393
+ form field it goes in) and `number`, the counter that solved it:
394
+
395
+ ```js
396
+ {
397
+ captchaId: '12345',
398
+ code: 'eyJhbGdvcml0aG0iOiJTSEEtMjU2Iiwi…',
399
+ token: 'eyJhbGdvcml0aG0iOiJTSEEtMjU2Iiwi…',
400
+ number: 9661,
401
+ }
402
+ ```
403
+
404
+ GeeTest additionally expands its answer into `challenge`, `validate`, and
405
+ `seccode` (`code` keeps the raw JSON string):
406
+
407
+ ```js
408
+ {
409
+ captchaId: '12345',
410
+ code: '{"geetest_challenge":"...","geetest_validate":"...","geetest_seccode":"..."}',
411
+ challenge: '...',
412
+ validate: '...',
413
+ seccode: '...',
414
+ }
415
+ ```
416
+
221
417
  ---
222
418
 
223
419
  ## SDK parameter aliases
@@ -231,6 +427,10 @@ Convenience aliases mapped before sending to CapSkip:
231
427
  | `minScore` | `min_score` |
232
428
  | `datas` | `data-s` |
233
429
  | `data_s` | `data-s` |
430
+ | `apiServer` | `api_server` |
431
+ | `api_subdomain` | `api_server` |
432
+ | `challengeUrl` / `challengeURL` | `challenge_url` |
433
+ | `challengeJson` / `challengeJSON` | `challenge_json` |
234
434
  | `proxy` object | `proxy` + `proxytype` strings |
235
435
 
236
436
  ```js
@@ -136,6 +136,7 @@ node examples/recaptcha.js
136
136
  | `image_captcha.js` | Image captcha from file, URL, or base64 |
137
137
  | `recaptcha.js` | reCAPTCHA v2, v3, invisible, enterprise, proxy |
138
138
  | `turnstile.js` | Cloudflare Turnstile widget and challenge page |
139
+ | `geetest.js` | GeeTest v3 slider, including fetching a fresh `gt`/`challenge` pair |
139
140
  | `async_example.js` | Parallel solving |
140
141
  | `verify_connection.js` | Check CapSkip is running |
141
142
 
@@ -152,6 +152,48 @@ const result = await solver.recaptcha('...', '...', { proxy });
152
152
 
153
153
  ---
154
154
 
155
+ ## GeeTest keeps failing with a bad-challenge error
156
+
157
+ **Symptom:** `geetest()` rejects with `ApiException` (or times out) even though
158
+ `gt` and `challenge` were copied correctly from the page.
159
+
160
+ **Cause:** The `challenge` value is **single-use and expires in about a minute**.
161
+ A pair copied out of DevTools minutes earlier, cached in a config file, or reused
162
+ across two solves is already dead.
163
+
164
+ **Fix:** Fetch a fresh pair programmatically immediately before each solve, and on
165
+ failure request a *new* pair rather than retrying the old one:
166
+
167
+ ```js
168
+ const { gt, challenge } = await (await fetch(REGISTER_URL)).json();
169
+ const result = await solver.geetest(gt, challenge, PAGE_URL);
170
+ ```
171
+
172
+ If the site loads GeeTest from a non-default API server domain, pass it through as well:
173
+
174
+ ```js
175
+ await solver.geetest(gt, challenge, url, { api_server: 'api-na.geetest.com' });
176
+ ```
177
+
178
+ ---
179
+
180
+ ## GeeTest result — where are challenge/validate/seccode?
181
+
182
+ `result.code` holds the raw JSON string CapSkip returns. The SDK also parses it
183
+ for you, so prefer the individual fields:
184
+
185
+ ```js
186
+ result.challenge; // geetest_challenge
187
+ result.validate; // geetest_validate
188
+ result.seccode; // geetest_seccode
189
+ ```
190
+
191
+ Submit all three under their `geetest_`-prefixed names, exactly as the site's own
192
+ front-end does. Sending only `validate` is the most common reason a correct solve
193
+ is rejected.
194
+
195
+ ---
196
+
155
197
  ## NetworkException during manual polling
156
198
 
157
199
  **Symptom:** `getResult()` keeps rejecting with `NetworkException`.