---
name: sadcaptcha
description: Solve TikTok, Douyin, Temu, Shein, Shopee, TikTok Live Studio, and sports-betting (Paripulse/1xbet/melbet soccer) captchas with SadCaptcha (sadcaptcha.com). Use when a browser automation or scraping task (Nodriver, Selenium, undetected-chromedriver, Playwright, Puppeteer, BAS, Appium) hits one of these captchas, or when the user mentions SadCaptcha, a SadCaptcha API key/license key, or one of its pip packages or Chrome extensions.
---

# SadCaptcha

SadCaptcha is a paid captcha-solving service. It comes in three forms, all using the same **API key** (also called `licenseKey`). Each successful solve costs credits from that key.

1. **Python packages.** One call launches a browser with the solver extension already loaded. Captchas get solved in the background automatically.
2. **Chrome extensions.** Unpacked MV3 extensions. Load one into any Chromium browser (by hand or programmatically) and it detects and solves captchas by itself. Use these for Node/Puppeteer, BAS, or any non-Python stack.
3. **REST API** at `https://www.sadcaptcha.com/api/v1`. You send base64 images and get back geometric solutions (ratios, angles, pixel offsets). You have to perform the drag or click yourself. Use this only when the first two options don't fit, e.g. mobile/Appium or a custom stack.

## Choosing an integration

| Situation | Use |
|---|---|
| Python + any browser framework | Python package (read the "SadCaptcha Python packages" section below) |
| Node.js / Puppeteer / Playwright-JS / BAS / other language, desktop browser | Chrome extension (read the "SadCaptcha Chrome extensions" section below) |
| Manual/no-code use in a normal browser | Chrome extension, loaded unpacked |
| Mobile (Appium), native apps, or a captcha variant no client handles | REST API (read the "SadCaptcha REST API" section below) |
| TikTok Live Studio desktop app | `tiktok-live-studio-captcha-solver` (see python-packages reference) |

Prefer the packages and extensions over the raw API. The hard parts (finding elements, downloading images, recording slide trajectories, humanlike mouse movement, retries) are already built into them. Several API endpoints (Temu arced slide, Shopee image crawl, Paripulse soccer) need you to drag the slider and record data at every pixel before you call them, so implementing them yourself is a lot of work.

## Products at a glance

| Site | pip package | import name | Chrome extension (GitHub `gbiz123/…`) | Captchas solved |
|---|---|---|---|---|
| TikTok / Douyin | `tiktok-captcha-solver` | `tiktok_captcha_solver` | `sadcaptcha-chrome-extensino` (sic) | rotate, puzzle slide, 3D shapes, icon (video upload) |
| Temu | `temu-captcha-solver` | `temu_captcha_solver` | `temu-captcha-solver-chrome-extension` | arced slide, puzzle, shapes, items, 3x3, swap two, cats & cars, two image (semantic ones are English only) |
| Shein | `shein-captcha-solver` | `shein_captcha_solver` | `shein-captcha-solver-chrome-extension` | image crawl, puzzle slide, icon, 3x3 ("nine") |
| Shopee | `shopee-captcha-solver` | `shopee_captcha_solver` | `shopee-captcha-solver-chrome-extension` | image crawl, puzzle slide, image drag |
| Sports betting (soccer "score a goal" slider: paripulse, 1xbet, melbet, linebet, starz888) | `sports-betting-captcha-solver` | `sports_betting_captcha_solver` | `sports-betting-captcha-solver-chrome-extension` | soccer slider |
| TikTok Live Studio (desktop) | `tiktok-live-studio-captcha-solver` | `tiktok_live_studio_captcha_solver` | none | Live Studio captchas via screen capture |

Extension zip URL pattern: `https://codeload.github.com/gbiz123/<repo>/zip/refs/heads/master`

## API key handling

- Ask the user for their key if you don't have it. Never invent one. Keys come from a sadcaptcha.com account.
- Read it from an environment variable (e.g. `SADCAPTCHA_API_KEY`) instead of hardcoding it. The Live Studio solver reads `SADCAPTCHA_API_KEY` natively.
- Check the remaining credits: `GET https://www.sadcaptcha.com/api/v1/license/credits?licenseKey=KEY` returns `{"credits": <int>}`.
- A patched extension folder contains the key in plain text in `script.js`. Don't commit it or share it.

## Minimal Python example (the usual answer)

```py
import asyncio, os
from tiktok_captcha_solver.launcher import make_nodriver_solver  # swap for temu_/shein_/shopee_/sports_betting_captcha_solver

async def main():
    browser = await make_nodriver_solver(
        os.environ["SADCAPTCHA_API_KEY"],
        browser_executable_path="/usr/bin/chromium-browser",  # must be Chromium, not Google Chrome
        browser_args=["--headless=new"],                       # omit for headed
    )
    page = await browser.get("https://www.tiktok.com/login")
    # ...normal automation; captchas get solved automatically in the background

asyncio.run(main())
```

## Rules that prevent most failures

1. **Use Chromium, not Google Chrome, when loading extensions programmatically.** Recent Google Chrome builds ignore `--load-extension`. The packages also add `--disable-features=DisableLoadExtensionCommandLineSwitch`. Do the same when you load an extension yourself.
2. **Headless:** use `--headless=new` or `--headless=chrome` as a launch *argument*. Plain `headless=True` doesn't run extensions. With Playwright, the package converts `headless=True` for you. Xvfb with a headed browser also works.
3. **Nodriver is recommended**, especially for Shein, Shopee, and the betting sites, which have strong bot detection. Selenium/Playwright work but get flagged more often.
4. **"Solved but verification failed"** almost always means the browser got fingerprinted. Use `undetected-chromedriver` or `playwright-stealth` with **default** settings. Don't spoof the user agent or change other fingerprint properties. With `playwright-stealth` on TikTok, pass `StealthConfig(navigator_languages=False, navigator_vendor=False, navigator_user_agent=False)` to avoid a white screen.
5. **Give the solver time.** After an action that triggers a captcha, wait (poll for the captcha to disappear or for the next page) instead of failing right away. One solve can take about 5–15 seconds, and it may retry with a refreshed challenge.
6. **Playwright contexts are persistent.** The helpers use `launch_persistent_context` with a temporary `user_data_dir` unless you pass one. Open pages with `context.new_page()`. Don't call `browser.new_context()`.

## Reference sections

The full references are included below in this file:
- **SadCaptcha Python packages**: per-package API (Nodriver, Selenium, Playwright sync/async, `ApiClient`, Live Studio).
- **SadCaptcha Chrome extensions**: adding the key, loading unpacked or programmatically, troubleshooting.
- **SadCaptcha REST API**: every endpoint with request/response JSON and how to use each answer.

Support: https://www.sadcaptcha.com, greg@sadcaptcha.com, Telegram @toughdata.

---

# SadCaptcha Python packages

Requires Python ≥ 3.10. Install the package for the target site:

```
pip install tiktok-captcha-solver          # TikTok / Douyin
pip install temu-captcha-solver            # Temu
pip install shein-captcha-solver           # Shein
pip install shopee-captcha-solver          # Shopee
pip install sports-betting-captcha-solver  # soccer slider on paripulse, 1xbet, melbet, linebet, starz888
pip install tiktok-live-studio-captcha-solver
```

The five browser packages share one interface. Replace `<pkg>` with `tiktok_captcha_solver`, `temu_captcha_solver`, `shein_captcha_solver`, `shopee_captcha_solver`, or `sports_betting_captcha_solver`.

Each factory function does the same thing:
1. Downloads the matching Chrome extension from GitHub into a temp dir.
2. Writes the API key into its `script.js`.
3. Launches a browser with `--load-extension=<dir>`.

After that the extension detects and solves captchas by itself. No other calls are needed. Keep using the returned browser/driver/context normally.

Framework prerequisites:
- Nodriver: Chromium installed (`pip install nodriver` comes as a dependency).
- Selenium: `undetected-chromedriver`; `selenium-stealth` is optional.
- Playwright: `playwright install chromium`; `playwright-stealth` is optional.

## Nodriver (recommended)

```py
import asyncio
from <pkg>.launcher import make_nodriver_solver   # note: import from .launcher

async def main():
    browser = await make_nodriver_solver(
        "YOUR_API_KEY",
        browser_executable_path="/usr/bin/chromium-browser",  # REQUIRED to be Chromium; Google Chrome won't load the extension
        browser_args=["--headless=new"],                       # optional; "--headless=chrome" also works
        # local_extension_directory="/path/to/unpacked-ext",   # optional: use a local extension instead of downloading
    )
    tab = await browser.get("https://www.example.com")
    # ... your automation

asyncio.run(main())
```

Signature: `async make_nodriver_solver(api_key, local_extension_directory=None, **nodriver_start_kwargs) -> nodriver.Browser`. The extra kwargs go straight to `nodriver.start()`. `browser_args` gets extended with the extension flags, not replaced.

## Selenium (undetected-chromedriver)

```py
from selenium.webdriver import ChromeOptions
from <pkg> import make_undetected_chromedriver_solver

options = ChromeOptions()
# options.add_argument("--headless=new")
driver = make_undetected_chromedriver_solver("YOUR_API_KEY", options=options)  # returns uc.Chrome
# optional: from selenium_stealth import stealth; stealth(driver)
driver.get("https://www.example.com")
```

Signature: `make_undetected_chromedriver_solver(api_key, options=None, **uc_chrome_kwargs) -> uc.Chrome`.

## Playwright (sync)

```py
from playwright.sync_api import sync_playwright
from <pkg> import make_playwright_solver_context

with sync_playwright() as p:
    context = make_playwright_solver_context(p, "YOUR_API_KEY", args=["--headless=new"])
    page = context.new_page()
    page.goto("https://www.example.com")
```

Signature: `make_playwright_solver_context(playwright, api_key, user_data_dir=None, **launch_persistent_context_kwargs) -> BrowserContext`.
- If you omit `args`, the package adds anti-detection flags for you (`--disable-blink-features=AutomationControlled`, `--no-sandbox`, `--start-maximized`, etc.). If you pass `args`, it only appends the extension flags.
- `headless=True` gets converted to `--headless=new` automatically.
- `user_data_dir` defaults to a temp directory that is deleted at exit. Pass a path to keep cookies between runs.

TikTok with playwright-stealth:
```py
from playwright_stealth import stealth_sync, StealthConfig
page = context.new_page()
stealth_sync(page, StealthConfig(navigator_languages=False, navigator_vendor=False, navigator_user_agent=False))
```

## Playwright (async)

```py
import asyncio
from playwright.async_api import async_playwright
from <pkg> import make_async_playwright_solver_context

async def main():
    async with async_playwright() as p:
        context = await make_async_playwright_solver_context(p, "YOUR_API_KEY", args=["--headless=new"])
        page = await context.new_page()
        await page.goto("https://www.example.com")
        # TikTok + stealth: from playwright_stealth import stealth_async, StealthConfig
        # await stealth_async(page, StealthConfig(navigator_languages=False, navigator_vendor=False, navigator_user_agent=False))

asyncio.run(main())
```

Same parameters as the sync version.

## Low-level `ApiClient` (TikTok)

`tiktok_captcha_solver.ApiClient` wraps the TikTok REST endpoints. Use it when you capture images yourself (e.g. with Appium):

```py
from tiktok_captcha_solver import ApiClient
client = ApiClient("YOUR_API_KEY")
client.rotate(outer_b64, inner_b64)        # -> .angle
client.puzzle(puzzle_b64, piece_b64)       # -> .slide_x_proportion
client.shapes(image_b64)                   # -> .point_one_proportion_x/_y, .point_two_proportion_x/_y
client.icon("Which of these objects has a brim?", image_b64)  # -> .proportional_points[i].proportion_x/_y
```

Temu, Shein, and Shopee also export an `ApiClient`, but their browser solvers are the supported path. For the endpoint math, see the "SadCaptcha REST API" section.

## TikTok Live Studio solver

This is a desktop tool, not a browser integration. It takes screenshots of the screen and moves the mouse with `pyautogui` while TikTok Live Studio is open.

```
pip install tiktok-live-studio-captcha-solver
SADCAPTCHA_API_KEY=... python -m tiktok_live_studio_captcha_solver   # prompts for the key if the env var isn't set
```

Requirements and caveats:
- Tkinter must be installed. On Linux it also needs `gnome-screenshot` and an **X11** session (Wayland won't work).
- It supports **one monitor only**.
- Windows: run the terminal as Administrator.
- macOS: grant the terminal app **Accessibility** and **Screen Recording** permission, then fully restart the terminal. Without these permissions the tool looks like it's running, but clicks do nothing and screenshots come out black.
- Keep the terminal open while streaming.
- Environment variables: `LOG_LEVEL=INFO|DEBUG` sets log verbosity, `LOG_IMAGES=true` saves captured images.

## Troubleshooting

- **Extension never acts:** you are almost certainly using Google Chrome. Point `browser_executable_path` (nodriver) or `executable_path` (Playwright) to Chromium. Also check that `github.com` is reachable, since the extension is downloaded when the browser launches. Otherwise pass `local_extension_directory` (nodriver only).
- **Captcha solved but "verification failed":** the browser got fingerprinted. Use nodriver, or undetected-chromedriver / playwright-stealth with default settings, and don't change the user agent.
- **Headless does nothing:** use the `--headless=new` arg, not `headless=True` (except with the Playwright helpers, which convert it).
- **Out of credits:** `GET https://www.sadcaptcha.com/api/v1/license/credits?licenseKey=KEY`.

---

# SadCaptcha Chrome extensions

These are Manifest V3 extensions. Once loaded and given an API key, they detect the site's captcha and solve it with no extra code. This is the recommended path for Node.js, Puppeteer, Playwright-JS, Browser Automation Studio (BAS), other languages, and no-code users.

| Site | GitHub repo (`https://github.com/gbiz123/<repo>`) | Runs on |
|---|---|---|
| TikTok / Douyin | `sadcaptcha-chrome-extensino` (spelled this way) | all URLs |
| Temu | `temu-captcha-solver-chrome-extension` | `https://*.temu.com/*` |
| Shein | `shein-captcha-solver-chrome-extension` | all URLs |
| Shopee | `shopee-captcha-solver-chrome-extension` | Shopee domains listed in `manifest.json` (.com, .tw, .co.th, .co.id, .com.my, .sg, .ph, .vn, .com.br, .mx, .cl, .co, .pl) |
| Sports betting soccer slider | `sports-betting-captcha-solver-chrome-extension` | all URLs |

Download a zip: `https://codeload.github.com/gbiz123/<repo>/zip/refs/heads/master` (strip the single top-level folder). You can also `git clone` it.

## 1. Provide the API key

The extension reads the key with `localStorage.getItem("sadCaptchaKey")` in `script.js`. There are two ways to provide it.

**A. Bake it into `script.js` (best for automation, since it persists across sessions and origins).** Run this in the extension directory:

```py
API_KEY = "YOUR_API_KEY"
with open("script.js", "r", encoding="utf-8") as f:
    script = f.read()
assert 'localStorage.getItem("sadCaptchaKey")' in script, "unexpected script.js; already patched?"
script = script.replace('localStorage.getItem("sadCaptchaKey")', f'"{API_KEY}"')
with open("script.js", "w", encoding="utf-8") as f:
    f.write(script)
```

Node equivalent:
```js
const fs = require("fs");
const p = "script.js";
fs.writeFileSync(p, fs.readFileSync(p, "utf8").replace('localStorage.getItem("sadCaptchaKey")', JSON.stringify(process.env.SADCAPTCHA_API_KEY)));
```

- Patch **`script.js`**, which is the file the manifest loads. Don't patch `script.ts`.
- For the Shopee and sports-betting extensions, `script.js`/`background.js` are compiled from TypeScript. If you rebuild (`npm install && npm run build`), patch *after* the build, because the build overwrites the patch. On Windows, the Shopee repo also includes `set-api-key.bat` (run it again and type `remove` to undo).
- The key ends up in plain text. Don't commit the patched folder or share it.

**B. Use the popup (manual use).** Open a page on the target site (for Shopee, a normal page, not the captcha page). Click the extension icon, paste the key, and press Submit. The key is stored in that origin's `localStorage`, so repeat this for each domain. Don't use this method for automation, because the solver reads the key as soon as a captcha appears.

## 2. Load it

### Manually
Go to `chrome://extensions`, turn on **Developer mode**, click **Load unpacked**, and select the extension folder. After you change files, click **reload** on the extension card.

### Programmatically
Required flags:
```
--load-extension=/abs/path/to/ext
--disable-extensions-except=/abs/path/to/ext
--disable-features=DisableLoadExtensionCommandLineSwitch
```
Use **Chromium** (or Chrome for Testing), because branded Google Chrome no longer honors `--load-extension`. Extensions don't run in old headless mode, so use `--headless=new` or run headed under Xvfb.

Puppeteer:
```js
const puppeteer = require("puppeteer");
const ext = "/abs/path/to/ext";
const browser = await puppeteer.launch({
  headless: false,            // or pass "--headless=new" in args
  args: [
    `--disable-extensions-except=${ext}`,
    `--load-extension=${ext}`,
    "--disable-features=DisableLoadExtensionCommandLineSwitch",
  ],
});
```

Playwright (Node): extensions need a persistent context.
```js
const { chromium } = require("playwright");
const ext = "/abs/path/to/ext";
const context = await chromium.launchPersistentContext("/tmp/profile", {
  headless: false,
  args: [
    `--disable-extensions-except=${ext}`,
    `--load-extension=${ext}`,
    "--disable-features=DisableLoadExtensionCommandLineSwitch",
  ],
});
const page = await context.newPage();
```

Selenium (any language): add the same three arguments to `ChromeOptions`. Don't use `add_extension()` with a .crx, because these are unpacked.

BAS and other anti-detect browsers: use the tool's "load unpacked extension" feature and point it at the patched folder.

## 3. Verify

- The extensions solve silently, and most of them log little or nothing. Judge success by the captcha itself: it should complete, and the page should move on without anyone touching it.
- The Shopee and sports-betting extensions use `chrome.debugger` to send trusted input. While a solve runs, Chrome shows a "started debugging this browser" bar. **That is expected.** Closing the bar aborts the solve. If the bar never appears, the service worker didn't attach, so reload the extension and then the page.
- A Shopee round takes about 10 seconds. About 1 in 6 rounds gets skipped on purpose (the page refreshes and no credit is charged).

## Troubleshooting

| Symptom | Cause / fix |
|---|---|
| Nothing happens at all | Extension not loaded (Google Chrome instead of Chromium, missing `--disable-features=DisableLoadExtensionCommandLineSwitch`, or old headless mode), or the content script doesn't match the URL. For Temu and Shopee, check the `matches` in `manifest.json` and add the domain if needed. |
| Captcha is dragged or clicked but never accepted | Bad or missing API key, or no credits left. Check with `GET https://www.sadcaptcha.com/api/v1/license/credits?licenseKey=KEY`. Otherwise it's bot detection: use a stealth setup with default fingerprint settings. |
| Worked, then stopped after a rebuild | The build overwrote the patched `script.js`. Patch it again. |
| Error "could not get sadCaptchaKey from localStorage" | The key was never set. Patch `script.js` or use the popup. |

---

# SadCaptcha REST API

Base URL: `https://www.sadcaptcha.com/api/v1`

Use the API directly only when the Python packages or Chrome extensions can't be used (mobile/Appium, native apps, or unusual stacks). The API solves images. **You still have to find the captcha elements, capture the images, and perform the clicks or drags yourself.** For complete working implementations, read the open-source clients at `github.com/gbiz123/<site>-captcha-solver` and the extension repos.

## Conventions

- Every solver endpoint is `POST <base>/<endpoint>?licenseKey=YOUR_API_KEY` with `Content-Type: application/json`.
- Images are **raw base64 strings with no `data:image/...;base64,` prefix**. You can get them by downloading the element's `src` (or CSS `background-image` URL) and base64-encoding the bytes, or by screenshotting the element.
- Request field names are **camelCase**. Several requests also accept snake_case aliases (listed below).
- Responses are **resolution-independent**: ratios (0–1), angles, or pixel offsets along the slider. Scale them against the *rendered* size of the element on the page (e.g. the bounding box), not the natural size of the image.
- Proportional points: `(0,0)` is the top-left of the image and `(1,1)` is the bottom-right. Pixel position = `element.x + proportionX * element.width`, `element.y + proportionY * element.height`.
- Each successful call deducts credits. `400 Bad Request` ("Could not process the provided image data.") means the images or payload couldn't be processed, e.g. wrong crop, wrong field names, or a data-URI prefix.
- Check credits: `GET <base>/license/credits?licenseKey=KEY` returns `{"credits": 123}`.

```py
import base64, requests
BASE = "https://www.sadcaptcha.com/api/v1"
KEY = "YOUR_API_KEY"
b64 = lambda path: base64.b64encode(open(path, "rb").read()).decode()
r = requests.post(f"{BASE}/puzzle", params={"licenseKey": KEY},
                  json={"puzzleImageB64": b64("puzzle.png"), "pieceImageB64": b64("piece.png")})
r.raise_for_status(); print(r.json())
```

## Endpoint index

| Endpoint | Site / captcha | Request body | Response |
|---|---|---|---|
| `/rotate` | TikTok rotate | `outerImageB64`, `innerImageB64` | `{angle}` |
| `/puzzle` | TikTok puzzle slide (generic puzzle slide) | `puzzleImageB64`, `pieceImageB64` | `{slideXProportion}` |
| `/shapes` | TikTok 3D shapes | `imageB64` | `{pointOneProportionX, pointOneProportionY, pointTwoProportionX, pointTwoProportionY}` |
| `/icon` | TikTok icon / video upload ("Which of these objects…") | `challenge`, `imageB64` | `{proportionalPoints: [...]}` |
| `/temu-arced-slide` | Temu arced slide | trajectory request | `{pixelsFromSliderOrigin}` |
| `/temu-swap-two` | Temu swap two tiles | `imageB64` | `{proportionalPoints: [press, release]}` |
| `/temu-two-image` | Temu two image (English) | `challenge`, `imagesB64: [left, right]` | `{proportionalPoints}` |
| `/temu-three-by-three` | Temu 3x3 | `objectsOfInterest`, `images` (9) | `{solutionIndices}` |
| `/semantic-shapes` | Temu shapes (English) | `challenge`, `imageB64` | `{proportionalPoints}` |
| `/semantic-items` | Temu items (English) | `challenge`, `imageB64` | `{proportionalPoints}` |
| `/image-semantics` | Any "text challenge + picture" (shapes, items, click-in-order / cats & cars; English) | `challenge`, `imageB64` | `{proportionalPoints}` |
| `/shein-icon` | Shein click icons in order | `challenge` (optional), `imageB64` | `{proportionalPoints}` (in click order) |
| `/shein-nine` | Shein 3x3 | `challengeText` **or** `baseImageB64`, `images` (9) | `{solutionIndices}` (ranked) |
| `/shopee-image-drag` | Shopee image drag | `puzzleImageB64`, `pieceImageB64` | `{proportionalPoints: [target]}` |
| `/shopee-image-crawl-pre-analyze` | Shopee image crawl (step 1) | `imageB64` | `{version, slideXProportion, skipRecommended}` |
| `/shopee-image-crawl` | Shopee image crawl (step 2) | trajectory request | `{pixelsFromSliderOrigin}` |
| `/paripulse-soccer-pre-analyze` | Soccer slider (step 1) | `imageB64` | `{leftOrRight, ballLocation, targetLocation, skipRecommended, shouldReleaseNow}` |
| `/paripulse-soccer` | Soccer slider (step 2) | `imagesPerLocation` | `{pixelsFromSliderOrigin}` |

`proportionalPoints` is an array of `{"proportionX": float, "proportionY": float}`.

---

## TikTok

### `/rotate`
```json
{"outerImageB64": "...", "innerImageB64": "..."}
```
Crop both images tightly to the circle edges with no surrounding whitespace. Outer is the ring image with the center removed, and inner is the circular center piece.
Response `{"angle": 0-360}`. Slide distance: `d = ((l_s - l_i) * angle) / 360`, where `l_s` is the slide bar width (`.captcha_verify_slide--slidebar`) and `l_i` is the slide button width (`.secsdk-captcha-drag-icon`).

### `/puzzle`
```json
{"puzzleImageB64": "...", "pieceImageB64": "..."}
```
Response `{"slideXProportion": 0.43}`. Slide distance: `d = slideXProportion * w`, where `w` is the rendered width of the puzzle image (`.captcha-verify-image`). Drag the slider button by `d` pixels.

### `/shapes`
```json
{"imageB64": "..."}
```
Response: two points (`pointOne…`, `pointTwo…`) given as proportions of the image. Click both.

### `/icon`
```json
{"challenge": "Which of these objects has a brim?", "imageB64": "..."}
```
Response `{"proportionalPoints": [...]}`. Click each point.

## Temu

### `/temu-arced-slide` and `/shopee-image-crawl` (trajectory captchas)
The piece moves along an unpredictable arc, and there are two candidate holes. Before calling the endpoint you must **hold the slider and sweep it across the whole bar**, recording at every pixel:
- `pixelsFromSliderOrigin` (int): how many pixels the slider button has moved from its origin.
- `pieceCenter`: center of the piece's bounding box as `proportionX`/`proportionY` of the puzzle image's width/height.
- `pieceRotationAngle` (float): the `rotate(...)` value from the piece element's `style`.

```json
{
  "puzzleImageB64": "...",
  "pieceImageB64": "...",
  "slidePieceTrajectory": [
    {"pixelsFromSliderOrigin": 0, "pieceRotationAngle": 0.0,      "pieceCenter": {"proportionX": 0.0869, "proportionY": 0.6826}},
    {"pixelsFromSliderOrigin": 1, "pieceRotationAngle": -0.841872, "pieceCenter": {"proportionX": 0.0869, "proportionY": 0.5478}}
  ]
}
```
snake_case aliases are also accepted: `puzzle_image_b64`, `piece_image_b64`, `slide_piece_trajectory`, `pixels_from_slider_origin`, `piece_rotation_angle`, `piece_center`, `proportion_x`, `proportion_y`.
Response `{"pixelsFromSliderOrigin": 187}`. Move the slider to that offset and then release. **Keep the mouse button held down** while you call the API. Release only after you have moved to the answer.

### `/temu-swap-two`
`{"imageB64": "..."}` returns two points. Press the mouse at point 1, drag to point 2, and release.

### `/temu-two-image` (English)
```json
{"challenge": "Please click on the corresponding characters in figure 1 in the order they appear from left to right in figure 2.",
 "imagesB64": ["<left b64>", "<right b64>"]}
```
Alias: `images_b64`. The response gives points to click, in order.

### `/semantic-shapes`, `/semantic-items`, `/image-semantics` (English)
```json
{"challenge": "Please click the unique object.", "imageB64": "..."}
```
Alias: `image_b64`. Pass the challenge text exactly as displayed. The response gives points to click, in order. `/image-semantics` is the general endpoint covering shapes, items, and click-in-order (cats & cars).

### `/temu-three-by-three`
For "Click on the corresponding images in the following order: 'television','strawberry','peach'":
```json
{"objectsOfInterest": ["television", "strawberry", "peach"],
 "images": ["<tile0>", "<tile1>", "...", "<tile8>"]}
```
Order the tiles row-major (left to right, top to bottom). Alias: `objects_of_interest`. Response `{"solutionIndices": [4, 0, 7]}`. Click those tiles in that order.

## Shein

### `/shein-icon`
The image is a CSS background. Get its URL with:
`window.getComputedStyle(document.querySelector('.pic_wrapper')).backgroundImage.match(/(?<=").*(?=")/)[0]`
```json
{"imageB64": "..."}
```
The response gives points in the order they must be clicked.

### `/shein-nine`
```json
{"challengeText": "bananas", "images": ["<tile0>", "...", "<tile8>"]}
```
Send **exactly one** of `challengeText` (text prompt) or `baseImageB64` (reference image prompt). Sending both or neither returns 400. Aliases: `challenge_text`, `base_image_b64`.
Response `{"solutionIndices": [2,5,6,0,1,3,4,7,8]}` lists **all** tiles ranked from best to worst match. Click the first N, where N is however many the challenge asks for.

## Shopee

### `/shopee-image-drag`
`{"puzzleImageB64": "...", "pieceImageB64": "..."}` returns one proportional point. Drag the piece to that point on the puzzle image.

### `/shopee-image-crawl-pre-analyze`, then `/shopee-image-crawl`
1. Send `{"imageB64": "<puzzle>"}` to pre-analyze. The response is `{"version": "v1"|"v2", "slideXProportion": 0.62, "skipRecommended": false}`.
   - If `skipRecommended` is true, refresh the captcha instead of solving it.
   - Otherwise `slideXProportion * sliderBarWidth` is the farthest you need to sweep. Use it to limit the trajectory recording.
2. Record the trajectory up to that distance and call `/shopee-image-crawl` (same request format as `/temu-arced-slide`, above). The response is `{"pixelsFromSliderOrigin"}`. Move there and release.

## Sports betting soccer slider (Paripulse, 1xbet, melbet, etc.)

### `/paripulse-soccer-pre-analyze`, then `/paripulse-soccer`
1. Send `{"imageB64": "<captcha screenshot>"}` to pre-analyze. The response is:
   ```json
   {"leftOrRight": "left"|"right", "ballLocation": {"proportionX":..,"proportionY":..},
    "targetLocation": {"proportionX":..,"proportionY":..}, "skipRecommended": false, "shouldReleaseNow": false}
   ```
   If `skipRecommended` is true, refresh the captcha.
2. Hold the slider and drag it toward `leftOrRight`. At each pixel, screenshot the captcha and record `mousePixelsFromCenter`: the mouse's offset from the slide bar's **center** (negative to the left, positive to the right).
   ```json
   {"imagesPerLocation": [{"imageB64": "...", "mousePixelsFromCenter": -120},
                          {"imageB64": "...", "mousePixelsFromCenter": -119}]}
   ```
   Send that to `/paripulse-soccer`. The response is `{"pixelsFromSliderOrigin"}`. Move there, then release. Don't release before you have the answer.

---

## Mobile / Appium pattern (TikTok)

1. `driver.save_screenshot()`.
2. Crop the captcha regions with PIL using device-specific boxes.
3. Base64-encode the crops and call the endpoint.
4. Convert the result to screen pixels and `driver.swipe(...)`.

Puzzle example: offset = `image_left_margin + image_width * slideXProportion`. Swipe from the center of the piece to `start_x + offset`, taking about 1000 ms.

Rotate example: offset = `((bar_width - button_width) * angle) / 360`.

Measure the box coordinates, margins, and bar widths on each device. The full worked examples are in the `tiktok-captcha-solver` README.

## Tips

- Take the images from the same render you will interact with. If the captcha refreshes, capture and call again.
- Move the mouse like a human, with eased motion, small jitter, and a short pause before releasing. Instant, perfectly straight drags get rejected even when the answer is right.
- Responses for identical image payloads are cached server-side, so resending the exact same images returns the same answer.
