Solve hCaptcha in Python.
The official Python SDK is one pip install nonecap away. Call
nc.solve() with the sitekey and page URL, get back a real hCaptcha
token. Sync or asyncio, fully typed, and signup comes with 100 free credits, so
you can test the whole flow before paying anything.
Where the solving actually happens
Not in your Python process. You send NoneCap a sitekey and a page URL; NoneCap solves
the challenge on its own servers and returns a normal P1_ token as a
string. There's no headless browser in your stack for hCaptcha to fingerprint and no
challenge UI for you to automate. Your script's only jobs are reading the sitekey off
the page and putting the token where the form expects it.
Quick start
pip install nonecap
Python 3.9+. The only dependency is httpx, and the package ships
py.typed, so mypy and pyright get full types with no stubs. Grab an API
key from dashboard.nonecap.com and the
headline is one call:
from nonecap import NoneCap
nc = NoneCap(api_key="nc_live_...")
# One call. solve() submits the captcha and long-polls
# until it's done, then returns the solved solve.
solve = nc.solve(
type="hcaptcha",
sitekey="10000000-ffff-ffff-ffff-000000000001",
url="https://example.com/login",
)
print(solve.token) # a real hCaptcha token, ready to submit nc.solve() submits the captcha and long-polls until the solve is
terminal: the server holds each request open for up to 90 seconds and the client
keeps re-attaching until the token arrives or its own timeout (180 seconds by
default) runs out. You don't write the polling loop.
When you want the loop yourself, the resource methods map one to one onto the REST API:
# Submit, holding the connection up to 30s, then poll the
# rest yourself. Drop wait= to return immediately instead.
pending = nc.solves.create(type="hcaptcha", sitekey=sitekey, url=url, wait=30)
done = nc.solves.retrieve(pending.id, wait=30)
# Cancel, list, or iterate every solve newest-first.
nc.solves.cancel(pending.id)
for s in nc.solves.list_all():
print(s.id, s.status)
# Your account and credit balance.
print(nc.me().credits_balance) Async with asyncio
AsyncNoneCap is the same surface, awaited. Use it as an async context
manager so the underlying httpx client gets closed for you:
import asyncio
from nonecap import AsyncNoneCap
async def main():
async with AsyncNoneCap(api_key="nc_live_...") as nc:
solve = await nc.solve(
type="hcaptcha",
sitekey="10000000-ffff-ffff-ffff-000000000001",
url="https://example.com/login",
)
print(solve.token)
asyncio.run(main())
Solving is I/O-bound on your side (the hard work happens on NoneCap's servers), so
asyncio is the natural fit for batches. One thing to size correctly: concurrency is
capped per account, and a naked asyncio.gather over a few hundred
targets will blow straight past it. A semaphore fixes that:
# Fan out a batch with asyncio.gather. Keep in-flight solves
# under your plan's concurrency cap (5 on the free trial,
# 25 Starter, 50 Builder, 100 Scale) with a semaphore.
sem = asyncio.Semaphore(5)
async def solve_one(nc, sitekey, url):
async with sem:
return await nc.solve(type="hcaptcha", sitekey=sitekey, url=url)
async with AsyncNoneCap(api_key="nc_live_...") as nc:
solves = await asyncio.gather(
*(solve_one(nc, sk, u) for sk, u in targets)
) Errors you can branch on
Everything the SDK raises extends NoneCapError, so you can catch the
whole family or pick out the case you care about: SolveFailedError
(with the full solve attached, including the underlying error code and timings),
SolveTimeoutError, RateLimitError,
InsufficientCreditsError, ValidationError (which carries
the offending param), and AuthenticationError:
from nonecap import (
NoneCap,
SolveFailedError,
SolveTimeoutError,
RateLimitError,
InsufficientCreditsError,
)
nc = NoneCap(api_key="nc_live_...")
try:
solve = nc.solve(type="hcaptcha", sitekey=sitekey, url=url)
except SolveFailedError as e:
# The full solve is attached: error code, timings, all of it.
print("could not solve it:", e.solve.error.code)
except SolveTimeoutError:
print("still pending after the client timeout (default 180s)")
except RateLimitError:
print("too many solves in flight, back off and retry")
except InsufficientCreditsError:
print("out of credits, top up at dashboard.nonecap.com")
Failed solves are never charged. The credits come back automatically, so a
SolveFailedError costs you a retry, not money.
No SDK? Plain requests works too
The API is ordinary REST, so if you'd rather not add a dependency, twenty lines of
requests does the same job. ?wait=90 makes the server hold
the connection until the solve reaches a terminal state, which means the token
usually comes back on the first response:
import requests
API = "https://api.nonecap.com/v1"
HEADERS = {"Authorization": "Bearer " + NONECAP_KEY}
r = requests.post(
f"{API}/solves?wait=90",
headers=HEADERS,
json={"type": "hcaptcha", "sitekey": sitekey, "url": url},
timeout=120,
)
data = r.json()
# wait=90 holds the connection until the solve is terminal, so the
# token usually comes back inline. A 202 means it's still in flight
# (pending or solving): keep polling GET /v1/solves/{id} until it isn't.
while data["status"] in ("pending", "solving"):
r = requests.get(
f"{API}/solves/" + data["id"] + "?wait=90",
headers=HEADERS,
timeout=120,
)
data = r.json()
if data["status"] != "solved":
raise RuntimeError("solve " + data["status"] + ": " + str(data.get("error")))
token = data["token"] # a real P1_... token
The same code works with httpx if that's already in your project. Full
request and response shapes are in the API reference.
Enterprise rqdata
Enterprise hCaptcha binds every challenge to a fresh, IP-bound rqdata
blob that the page passes into hcaptcha.render() or
execute(). Capture it from the live page and forward it with the
enterprise type. The blob goes stale fast, so capture it right before you solve:
# rqdata is required for enterprise, and mypy/pyright enforce it:
# forgetting it on type="hcaptcha_enterprise" is a type error,
# not a runtime surprise.
solve = nc.solve(
type="hcaptcha_enterprise",
sitekey=sitekey,
url=url,
rqdata=rqdata, # the fresh, IP-bound challenge blob
) You have a token. Now what?
Two paths, depending on whether there's a browser in your stack.
Driving a browser? Set the value of
textarea[name="h-captcha-response"] on the page, plus the
g-recaptcha-response compatibility field if it exists (hCaptcha reuses
reCAPTCHA's old field name; the value is still the hCaptcha token), then fire the
widget callback for invisible sitekeys and submit. The
Selenium guide and the
Playwright guide cover the injection details for
each driver, so this page won't repeat them.
Pure HTTP? Even simpler. Include the token as a form field in the POST you were going to send anyway:
# Pure-HTTP flow: put the token in the form POST you were
# going to send anyway. The g-recaptcha-response field is
# hCaptcha's compatibility name, same token in both.
requests.post(
"https://target.example/login",
data={
"email": email,
"password": password,
"h-captcha-response": token,
"g-recaptcha-response": token,
},
)
One rule either way: don't hard-code the sitekey. Sites rotate them, and enterprise
deployments bind challenges to the page. Read it fresh from the live page, either
the [data-sitekey] attribute on the widget or the sitekey
query param on the hcaptcha.com/1/api.js script tag.
When this isn't the right approach
NoneCap solves hCaptcha only: regular, invisible, and enterprise
rqdata. If the page is actually protected by reCAPTCHA, Cloudflare Turnstile, or FunCaptcha, this technique doesn't apply; those are on the roadmap, not live. And if your target has no captcha at all, skip the token step and POST the form directly.
Pairing the solve with a browser? See hCaptcha in Selenium or hCaptcha in Playwright for the injection side. Running a no-browser pipeline at scale, read the web scraping guide; building autonomous tools, the AI agents guide. And if your project is TypeScript rather than Python, the same client exists for Node: npm install nonecap.
Last updated June 2026.
Frequently asked
Does the SDK have real type hints?
py.typed with full inline types, so mypy, pyright, and your IDE pick them up with zero config. The types do actual work: solve() is overloaded so rqdata is required when type="hcaptcha_enterprise" and optional for plain "hcaptcha". Forget it on an enterprise solve and the type checker fails the build before you ever burn a credit.Can I solve captchas concurrently with asyncio?
AsyncNoneCap has the same surface as the sync client, awaited, so asyncio.gather fan-out works the way you would expect. Concurrency is capped per account (5 on the free trial, 25 on Starter, 50 on Builder, 100 on Scale), so wrap the calls in an asyncio.Semaphore sized under your cap. If you push past it you will see RateLimitError: back off and retry.Does this work with plain requests or httpx, without a browser?
h-captcha-response (or g-recaptcha-response) in the form POST your script was already sending. The web scraping guide walks through the full no-browser pipeline.What Python versions and dependencies does it need?
httpx, which also powers the async client. The SDK is open source at github.com/nonecap/nonecap-py and published on PyPI as nonecap.