The hard part of a mathematical captcha is not the arithmetic — it is automating "image in, answer out". Here is the whole path in order: grab the image, submit it, poll for the answer, type it back, and what to do when it comes back wrong.
Locate the image in your page or request first. In a browser it is usually an <img id="captchaImage">; older sites may keep it in a <canvas> or as a CSS background. Drawing whatever you found into a canvas and exporting base64 is the most reliable way to get it.
// grab the challenge image and turn it into base64
const img = document.querySelector('#captchaImage')
const canvas = document.createElement('canvas')
canvas.width = img.naturalWidth || img.width
canvas.height = img.naturalHeight || img.height
canvas.getContext('2d').drawImage(img, 0, 0)
const base64 = canvas.toDataURL('image/png').replace(/^data:image/png;base64,/, '')
Most mathematical captchas hand out a single image, so one element in image_data is enough.
Mathematical captchas are the textcaptcha type in the API: send the image, get the answer back as a string.
curl -X POST https://fuckcaptcha.top/v1/recognition/textcaptcha \
-H 'Authorization: Basic nsk_YOUR_KEY' \
-H 'Content-Type: application/json' \
-d '{"image_data":["<base64 from step 1>"]}'
# -> {"data":"nsj_f350ba75d856c66b0b924340"}
An accepted submission returns a job id (data) immediately and charges one credit; a submission that is refused is not charged.
Recognition is asynchronous and an arithmetic image usually finishes in one to three seconds. While polling, error 14 means it is still working, so ask again in 500ms.
# error 14 means it is still working — ask again in 500ms
curl "https://fuckcaptcha.top/v1/recognition/textcaptcha?id=nsj_f350ba75d856c66b0b924340&key=nsk_YOUR_KEY"
# pending -> {"error":14,"message":"Incomplete job"}
# finished -> {"data":["7"],"metadata":{}}
The data field is an array; its first element is the answer string, for example "7".
Put the answer into the input box and fire an input and a change event yourself — many legacy forms only enable submit after those events.
// type the answer back, and let the form know it changed
const input = document.querySelector('#captchaInput')
input.value = '7' // data[0] from the poll response
input.dispatchEvent(new Event('input', { bubbles: true }))
input.dispatchEvent(new Event('change', { bubbles: true }))
Here are the four steps in one place; change the key and the image source and it runs.
import base64, time, requests
KEY, API = "nsk_YOUR_KEY", "https://fuckcaptcha.top"
def solve(image_bytes):
b64 = base64.b64encode(image_bytes).decode()
job = requests.post(f"{API}/v1/recognition/textcaptcha",
json={"image_data": [b64]},
headers={"Authorization": f"Basic {KEY}"}, timeout=30).json()["data"]
for _ in range(30):
time.sleep(1)
r = requests.get(f"{API}/v1/recognition/textcaptcha",
params={"id": job, "key": KEY}, timeout=30).json()
if "error" not in r:
return r["data"][0] if isinstance(r["data"], list) else r["data"]
if r["error"] != 14:
raise RuntimeError(r) # 12, 13, 16 ... are terminal
raise TimeoutError("no answer within 30s")
The browser extension recognises these images and fills the answer in automatically, using the same allowance as the API. If you only hit a mathematical captcha occasionally, the extension is cheaper than maintaining a scraper.
That means the job is still being worked on. An arithmetic image usually finishes in one to three seconds; if it is still 14 after a minute, submit a fresh image (each accepted job is charged one credit).
No. The endpoint returns the final answer (7); converting words and doing the addition both happen on the recognition side.
Recognition only needs the image. Fetching it and typing the answer back both happen inside your own session — we do not need your cookies or account.
A submission that is refused is not charged; once a job is accepted it costs one credit, so retrying is best done with a fresh image.