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

Terminal
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:

Python · sync
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:

Python · lower-level
# 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:

Python · asyncio
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:

Python · fan-out
# 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:

Python · error handling
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:

Python · requests, no SDK
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:

Python · enterprise
# 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:

Python · submit the token
# 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?
Yes. The package ships 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?
Yes. 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?
Yes. The token is just a string. NoneCap solves the challenge on its own servers, so you never need a browser at all: include the returned token as 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?
Python 3.9 or newer. The only dependency is httpx, which also powers the async client. The SDK is open source at github.com/nonecap/nonecap-py and published on PyPI as nonecap.
How much does each solve cost?
At least one credit per solve. Billing is one credit per hCaptcha challenge round (hCaptcha decides how many: often one, sometimes two or three), charged only on success, and failed solves are auto-refunded. Credits run $0.40 to $0.50 per 1,000 depending on your pack, you get 100 free credits on signup, and there is no subscription. See pricing.

Start solving hCaptcha in minutes.

100 free credits on signup. Pay per solve, credits never expire, failed solves auto-refunded.