The official NoneCap TypeScript SDK.

nonecap is the official TypeScript client for the NoneCap hCaptcha solver API, and the shortest way to solve hCaptcha in Node.js. Submit a captcha, get back a token. The SDK handles the polling, the timeouts, and the error cases so you don't write the fetch loop yourself. Zero dependencies, and the types do real work.

Install

Terminal
npm install nonecap

Runs on Node 18+, Bun, Deno, and edge runtimes. Get an API key from dashboard.nonecap.com. Rather not add a dependency? The plain fetch section below drives the REST API directly.

Quick start

The headline is a single call. solve() submits the captcha and waits until it's done, then returns the solved solve:

Node · TypeScript
import { NoneCap } from "nonecap";

const nc = new NoneCap({ apiKey: process.env.NONECAP_KEY });

// Submit a captcha and wait for the token. One call, no polling loop.
const solve = await nc.solve({
  type: "hcaptcha",
  sitekey: "10000000-ffff-ffff-ffff-000000000001",
  url: "https://example.com/login",
});

console.log(solve.token); // a real hCaptcha token, ready to submit

Errors you can branch on

Every error this library throws extends NoneCapError, so you can catch the whole family or pick out the one you care about. A failed solve throws SolveFailedError with the full solve attached, so you get the underlying error code and the timings:

Node · TypeScript
import {
  NoneCap,
  SolveFailedError,
  InsufficientCreditsError,
  RateLimitError,
} from "nonecap";

try {
  const { token } = await nc.solve({ type: "hcaptcha", sitekey, url });
  // use token
} catch (err) {
  if (err instanceof SolveFailedError) {
    // What happened, what to do, and that nothing was charged.
    console.error(err.solve.error?.message);
    if (err.retryable) scheduleRetry();
  } else if (err instanceof InsufficientCreditsError) {
    console.error("Out of credits. Top up at dashboard.nonecap.com");
  } else if (err instanceof RateLimitError) {
    await sleep((err.retryAfter ?? 1) * 1000);
  } else {
    throw err;
  }
}

Enterprise sitekeys

Pass type: "hcaptcha_enterprise" for an enterprise sitekey. rqdata is optional: many enterprise sites never issue one. When the page does pass an rqdata blob to its widget, send it, fresh for each challenge:

Node · TypeScript
const { token } = await nc.solve({
  type: "hcaptcha_enterprise",
  sitekey,
  url,
  rqdata, // optional: the fresh challenge blob, when the site issues one
});

Zero dependencies: plain fetch

The API is ordinary REST and fetch has been global since Node 18, so you can skip the package entirely. ?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. The loop only matters for the occasional solve that outlives one 90-second window:

Node · fetch, no SDK
// Node 18+: fetch is global, so this file has zero dependencies.
const API = "https://api.nonecap.com/v1";
const headers = {
  Authorization: `Bearer ${process.env.NONECAP_KEY}`,
  "Content-Type": "application/json",
};

async function solveHcaptcha(sitekey, url) {
  // wait=90 holds the connection until the solve is terminal,
  // so the token usually comes back on the first response.
  let res = await fetch(`${API}/solves?wait=90`, {
    method: "POST",
    headers,
    body: JSON.stringify({ type: "hcaptcha", sitekey, url }),
  });
  let solve = await res.json();

  if (!res.ok) {
    // 401 bad key, 402 out of credits, 422 bad params, 429 over
    // your concurrency cap. The body says which.
    throw new Error(`HTTP ${res.status}: ${JSON.stringify(solve)}`);
  }

  // A 202 means the solve is still in flight (pending or solving):
  // keep re-attaching with GET until it reaches a terminal state.
  while (solve.status === "pending" || solve.status === "solving") {
    res = await fetch(`${API}/solves/${solve.id}?wait=90`, { headers });
    solve = await res.json();
  }

  if (solve.status !== "solved") {
    throw new Error(`solve ${solve.status}: ${solve.error?.code}`);
  }
  return solve.token; // a real P1_... token
}

const token = await solveHcaptcha(
  "f5ab1c2d-7e8f-4a9b-b1c2-d3e4f5a6b7c8",
  "https://example.com/login",
);

Full request and response shapes, including the error body format, are in the API reference.

Long jobs: submit now, collect later

Holding a connection open is the simple default, but it ties up the caller. If you're queueing solves from a job worker, or you just don't want a request hanging for tens of seconds, create the solve without ?wait and collect the result separately:

Node · submit then poll
// Submit without wait= and the API returns the pending solve
// immediately. Poll it on your own schedule.
let res = await fetch(`${API}/solves`, {
  method: "POST",
  headers,
  body: JSON.stringify({ type: "hcaptcha", sitekey, url }),
});
let solve = await res.json(); // { id: "solve_01HQF...", status: "pending", ... }

// Later, from anywhere that has the id:
res = await fetch(`${API}/solves/${solve.id}?wait=30`, { headers });
solve = await res.json();

You have a token. Now what?

The token goes into the form field named h-captcha-response. In a pure-HTTP pipeline that's one extra field in the POST you were already sending:

Node · submit the token
// Pure-HTTP flow: put the token in the form POST you were
// going to send anyway. g-recaptcha-response is hCaptcha's
// compatibility field name, same token in both.
await fetch("https://target.example/login", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    email,
    password,
    "h-captcha-response": token,
    "g-recaptcha-response": token,
  }),
});

Driving a browser instead? Set the value of textarea[name="h-captcha-response"] on the page, plus the g-recaptcha-response compatibility field if it exists, then fire the widget callback for invisible sitekeys and submit. The Puppeteer guide and the Playwright guide cover the injection details for each driver. And if you want the mechanics of what's inside a token, why it's single-use, and why it expires after roughly two minutes, that's the hCaptcha token deep dive.

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.

Lower-level methods

solve() is the convenient path. When you want control over submission and polling, the resource methods map one to one to the REST API:

Node · TypeScript
// Submit without blocking, then poll the one solve yourself.
const pending = await nc.solves.create({ type: "hcaptcha", sitekey, url });
const done = await nc.solves.retrieve(pending.id, { wait: 30 });

// Cancel, list, or iterate every solve newest-first.
await nc.solves.cancel(pending.id);
for await (const s of nc.solves.listAll()) console.log(s.id, s.status);

// Your account and credit balance.
const me = await nc.me();
console.log(me.credits_balance);

// Tell us when your target rejects a token — free, and it tunes minting
// against your real acceptance rate. Reporting accepted ones is optional.
// submitToTarget is your own form post; true when the target took the token.
const targetAccepted = await submitToTarget(done.token);
if (!targetAccepted) {
  await nc.feedback.report({ solve_id: done.id, outcome: "rejected" });
}

Python

There's an official Python client too: pip install nonecap. Same shape as the TypeScript SDK, with sync and async clients, the typed error tree, and full inline types for mypy and pyright. Source at github.com/nonecap/nonecap-py, full guide at Python hCaptcha solver.

Python
# pip install nonecap
from nonecap import NoneCap

nc = NoneCap(api_key="nc_live_...")

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

# Async? Same surface: AsyncNoneCap, awaited.

Not using JavaScript or Python?

The API is a plain REST endpoint, so any language works. The API reference has runnable examples in cURL, Node, Python, and Go, the Go hCaptcha solver guide builds a complete client on net/http, and the integration guides cover Playwright, Puppeteer, web scraping, and AI agents. Prefer a no-code option? The Chrome extension solves hCaptcha right in your browser.

When this isn't the right approach

NoneCap solves hCaptcha only: regular, invisible, and enterprise rqdata sitekeys. If the page is actually protected by reCAPTCHA, Cloudflare Turnstile, or FunCaptcha, this guide 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.

Last updated October 2026.

Frequently asked

What runtimes does it work on?
Node 18 and newer, plus Bun, Deno, and edge runtimes. It uses the built-in fetch and ships zero dependencies. The package is dual ESM and CommonJS with bundled type definitions.
Does it handle polling and timeouts for me?
Yes. nc.solve() submits the captcha and uses the API's long-poll under the hood, keeping the connection open until the solve finishes or your timeout elapses. It returns the solved solve, or throws a typed error you can branch on.
How are errors typed?
Every error extends NoneCapError, so you can catch the whole family or a specific one: AuthenticationError, InsufficientCreditsError, ValidationError (with the offending param), RateLimitError, NotFoundError, ConflictError, and more. A failed solve throws SolveFailedError with the full solve attached.
How much does each solve cost?
At least one credit per solve. Billing starts at one credit per hCaptcha challenge round (hCaptcha decides how many: often one, sometimes two or three), charged only on success, so failed solves are never charged. Credits run $0.20 to $0.50 per 1,000 depending on your pack, you get 1,300 free credits on signup, and no subscription is required. See pricing.
How many solves can I run in parallel?
Concurrency is capped per account: 5 solves in flight on the free trial, 25 on Starter, and 50 on Builder, Scale and Max. The Professional plan gives you 100 at once, and Enterprise 200. Promise.all over a big array will blow past the cap and the extra requests come back 429 (the SDK throws RateLimitError), so run the batch through a small pool, for example p-limit sized at or under your cap, and retry anything that got rate-limited.
Do I need a headless browser in my Node app?
No. The token is just a string. NoneCap solves the challenge on its own servers, so a pure fetch pipeline can include the token as h-captcha-response in the form POST it was already sending. You only need Puppeteer or Playwright when the rest of the job needs a page; the Puppeteer guide and Playwright guide cover injecting the token there.
Is it open source?
Yes, MIT licensed. The source is at github.com/nonecap/nonecap-js.

Start solving hCaptcha in minutes.

1,300 free credits on signup. Pay per solve, credits never expire, failed solves never charged.