NoneCap API · v1
Get a key →
01 · getting started

The NoneCap hCaptcha API.

A RESTful, JSON-over-HTTPS API for solving hCaptcha programmatically. Send a sitekey and the page URL; we return a real P1_ token your client submits as h-captcha-response: the same shape hCaptcha hands back after a human solves the challenge.

Base URL
api.nonecap.com/v1
Auth scheme
Bearer token
Content type
application/json
Captcha
hCaptcha

Image-recognition solvers produce low-trust tokens that hCaptcha enterprise flags within seconds. NoneCap returns a finished token that passes hCaptcha's server-side checks, so it actually authorises the request it was issued for.

All endpoints live under https://api.nonecap.com. HTTPS only, JSON in and out, and every request carries authentication.

Quickstart

  1. Sign in to the dashboard and mint an API key (nc_live_…).
  2. POST a solve to /v1/solves. Add ?wait=N (1-90 s) to block up to N seconds and return as soon as the solve reaches a terminal state. If the solve is still in flight when that window closes you get a 202 with a null token, so poll GET /v1/solves/{id} until it's terminal.
  3. Inject the returned P1_ token into the target form's h-captcha-response field and submit.
  4. Report the tokens your target refuses to /v1/feedback — send the solve_id with an outcome of rejected. A token can pass hCaptcha and still be refused by your target, and this is the only signal that shows us that. Reporting accepted tokens too is optional and sharpens the read. Feedback reference

Authentication

You mint API keys in the dashboard. Format: nc_live_ + 32 base64url characters. Send it on every request:

Authorization: Bearer nc_live_a8f2K9...

Errors

  • 401 unauthorized: missing, malformed, revoked, or unknown key.
  • 402 insufficient_credits: valid key but balance < 1; only raised on calls that consume credits.
  • 402 key_credit_limit_exceeded: the key reached its optional per-key credit limit; raise or reset it on the Keys page.

Sign up in the dashboard to mint a key and claim 1,300 free credits. A future nc_test_ prefix is reserved for test-mode keys.

Errors

Every non-2xx response follows the same envelope:

{
  "error": {
    "code":    "insufficient_credits",
    "message": "Your account has no credits remaining. Top up at https://dashboard.nonecap.com/billing.",
    "param":   null,
    "request_id": "9f1c2d3e-4b5a-4c6d-8e7f-0a1b2c3d4e5f"
  }
}

param names the offending request field for validation errors; otherwise null. request_id (also the X-Request-Id response header) is the id to quote to support.

HTTP status codes

400
BAD REQUEST
invalid_request: The request body is not a JSON object, or is not valid JSON.
401
UNAUTHORIZED
unauthorized: The Authorization header is missing or malformed, or the API key is unknown or revoked.
402
PAYMENT REQUIRED
insufficient_credits: The account does not have the 1 credit a solve needs to start. Top up on the dashboard.
402
PAYMENT REQUIRED
key_credit_limit_exceeded: This API key has reached its own credit limit. Raise or reset it on the dashboard Keys page.
403
FORBIDDEN
account_locked: The owning account is locked. Contact support.
403
FORBIDDEN
forbidden: The API key lacks the scope this endpoint needs, the field is admin-only, or requests from your network address are refused (the message says which).
404
NOT FOUND
not_eligible: Feedback names a solve that is unknown, belongs to another account, or did not end in solved.
404
NOT FOUND
not_found: No solve with that id belongs to this account (ids are private per account), or no route matches the method and path.
409
CONFLICT
conflict: The solve is already in a terminal state, so it cannot be cancelled.
413
TOO LARGE
payload_too_large: The request body exceeds the route's size limit; the message names the limit.
415
UNSUPPORTED MEDIA
unsupported_media_type: A JSON route was called without Content-Type: application/json.
422
UNPROCESSABLE
expired_window: The first feedback for this solve arrived after the reporting window closed.
422
UNPROCESSABLE
validation_error: A field has a bad value; param names it.
429
RATE LIMITED
concurrency_limit_exceeded: Too many solves in flight for this account. Wait for one to finish (Retry-After is set) or ask support to raise the limit.
429
RATE LIMITED
sitekey_rate_limited: hCaptcha is refusing to serve challenges for this sitekey, so new submits for it are shed instead of run. Wait the Retry-After seconds; the message says whether a different proxy pool can help.
500
SERVER ERROR
internal_error: Something failed on our side. The message carries a request id; quote it to support if it persists.
503
UNAVAILABLE
maintenance: New solves are paused for maintenance. Retry shortly.

Solve-failure codes

These ride in Solve.error.code on a terminal, non-solved solve. status is failed, cancelled, or expired. The request succeeded; the solve didn't produce a token, and nothing was charged. The message differs by whether you supplied a proxy: the first sentence below is the proxied wording, the second the proxyless one.

challenge_not_loaded
retryable
hCaptcha did not serve a challenge through your proxy. This usually means the proxy's IP has poor reputation with hCaptcha. Retry with a different proxy or exit IP. This solve was not charged.
Proxyless: hCaptcha did not serve a challenge this time. Retry; if it keeps happening for this sitekey, contact support with the solve id. This solve was not charged.
token_not_granted
retryable
The challenge was answered but hCaptcha did not issue a token. If it persists, use a proxy whose IP has better reputation with hCaptcha. This solve was not charged.
Proxyless: The challenge was answered but hCaptcha did not issue a token. Retry; if it keeps happening for this sitekey, contact support with the solve id. This solve was not charged.
challenge_expired
retryable
hCaptcha expired the challenge before the solve completed. Retry the solve. This solve was not charged.
challenge_errored
retryable
hCaptcha aborted the challenge before the solve completed. Retry the solve. This solve was not charged.
proxy_error
not retryable
Your proxy could not be used: it failed to connect, or hCaptcha refused its IP. Check the proxy host and credentials, or switch to a different exit IP. This solve was not charged.
Proxyless: NoneCap's network could not reach hCaptcha this time. Retry the solve. This solve was not charged.
target_unreachable
not retryable
The target url could not be loaded. Verify it is reachable. This solve was not charged.
vision_error
retryable
The challenge was served but could not be solved this time. Retry; if it persists for one challenge type, contact support with the solve id. This solve was not charged.
internal_error
retryable
Something failed on NoneCap's side before the solve completed. Retry; if it persists, contact support with the solve id. This solve was not charged.
unsolvable_variant
not retryable
This challenge is in a format NoneCap cannot currently solve. This solve was not charged.
capacity_exhausted
retryable
NoneCap is momentarily at capacity for this challenge type. Retry in a few seconds. This solve was not charged.
cancelled
not retryable
Cancelled by the caller. This solve was not charged.
expired
retryable
Your proxy did not respond in time, so the solve reached its deadline. Check the proxy, or switch to a different exit IP. This solve was not charged.
Proxyless: The solve did not complete before its deadline. Retry; if it keeps happening for this sitekey, contact support with the solve id. This solve was not charged.
cancelled
not retryable
Cancelled by the caller. This solve was not charged.
challenge_not_loaded
When hCaptcha answered 429 before a challenge was served: hCaptcha refused to serve a challenge for this sitekey (HTTP 429, before the solve began). Three causes look identical from here: the sitekey as a whole is being limited (retry later; a different IP will not help), your IP pool is (try a different proxy pool), or the site only serves visitors from its own country (use a proxy located there, or for proxyless solves ask support to pin the exit country for this sitekey). This solve was not charged.

Solve-failure reasons

error.reason is a typed sub-reason within error.code, set when the solver knows more than the code says; null otherwise. error.retryable says whether re-submitting the same request unchanged can succeed.

sitekey_rate_limited
challenge_not_loaded
hCaptcha answered 429 before serving a challenge; the message says which of the three shapes it can be.
passive_required
token_not_granted
The sitekey is configured to pass without a challenge and did not issue a token this time.
rounds_exhausted
token_not_granted
hCaptcha kept serving new challenges without accepting an answer, so the solve was stopped.
proxy_rejected
proxy_error
Your proxy refused the connection (credentials or source IP); the message quotes its reply. Not retryable.
proxy_tls
proxy_error
Your proxy intercepts or rewrites TLS, so hCaptcha cannot be reached through it. Not retryable.
proxy_stalled
expired
Your proxy accepted the connection but did not answer within the solve deadline. Not retryable.
proxy_egress_blocked
proxy_error
Your proxy points at a private or internal network address, so no connection was attempted. Not retryable.
target_egress_blocked
target_unreachable
The target url points at a private or internal network address, so the page was never opened. Not retryable.

Versioning

This is v1. Breaking changes will ship under v2 with at least 12 months of v1 support.

Resource shapes are additive within a major version: new optional fields can appear at any time. Clients should ignore unknown fields rather than treating them as an error.

Pagination

List endpoints use cursor-based pagination. The cursor is the id of the last item you received; pass it as starting_after on the next request.

  • limit: 1-100, default 20.
  • starting_after: a solve_… id (exclusive cursor).
  • Responses carry has_more: boolean. There is no separate cursor field: read the last item's id.
02 · solves

The solve object

A solve represents one captcha-solving job: its parameters, the worker that picked it up, the resulting token, and how much it cost you.

Attributes

id string optional
Prefixed ULID, e.g. solve_01HQF7K3JKWZX.
object "solve" optional
Constant.
type string · enum optional
Captcha type. See Captcha types below.
hcaptchahcaptcha_enterprise
status string · enum optional
Current state. Terminal states are solved, failed, cancelled and expired.
pendingsolvingsolvedfailedcancelledexpired
sitekey string optional
Echoed from the original request.
url string optional
Echoed from the original request.
token string · nullable optional
Present iff status = solved.
resp_key string · nullable optional
hCaptcha's response key for the challenge behind token — the value the widget's hcaptcha.getRespKey() returns (E0_…). Sites that verify the token and key together need both. Present with token; null otherwise.
error object · nullable optional
Present on failed, cancelled, and expired; null otherwise. Carries code, a customer-readable message (what happened, what to do, whether it was charged), a typed reason or null, retryable, and docs_url. See Errors.
credits_charged integer · nullable optional
Final billed amount. null until terminal.
proxy_bytes integer · nullable optional
Bytes that egressed through your metered proxy for this solve. null when the solve ran without a proxy.
created_at string · RFC 3339 optional
When the job was accepted.
started_at string · nullable optional
When a worker picked it up.
finished_at string · nullable optional
When the solve reached a terminal state.
queue_ms integer · nullable optional
Milliseconds spent waiting in the queue before a worker picked the solve up (started_at − created_at). For a solve that went terminal without ever being picked up (expired or cancelled while still queued), this is the full wait (finished_at − created_at). null while still pending.
resolve_ms integer · nullable optional
Milliseconds of actual solving (finished_at − started_at). null if the solve never left the queue, or hasn't finished yet. Pair with queue_ms to tell capacity (queue) from solver performance (resolve).

Lifecycle

pending → solving → solved
                  → failed
                  → expired     (exceeded time budget / reclaimed)

pending | solving → cancelled   (via DELETE)

Records are purged after a 30-day retention window; after that GET /v1/solves/{id} returns 404. That retention is separate from the expired status above.

Captcha types

hcaptcha captcha type optional
Required: sitekey, url. Optional: proxy. (user_agent is accepted but deprecated and ignored.) Returns a P1_ token.
hcaptcha_enterprise captcha type optional
Required: sitekey, url, rqdata. Optional: proxy. (user_agent is accepted but deprecated and ignored.) Returns a P1_ token.
recaptcha_v2 reserved optional
Reserved for future expansion.
recaptcha_v3 reserved optional
Reserved for future expansion.
cloudflare_turnstile reserved optional
Reserved for future expansion.
funcaptcha reserved optional
Reserved for future expansion.

Create a solve

POST https://api.nonecap.com /v1/solves

Submit a new captcha solve. You're charged only when a solve succeeds (the amount is metered per round and reported as credits_charged); failed, cancelled, and expired solves are never charged. Submitting requires a balance of at least 1 credit.

Body parameters

type string required
Captcha type to solve.
hcaptchahcaptcha_enterprise
sitekey string required
The sitekey hCaptcha gave you.
url string required
Page URL where the challenge appears.
rqdata string optional
Required for hcaptcha_enterprise; carries the IP-bound challenge data. Send a matching sticky proxy with it (see below).
user_agent string optional
Deprecated and ignored. Still accepted, so existing integrations keep working, but it no longer reaches the wire. Every solve now presents its own coherent browser identity: the user agent, the client hints, the language header and the proof-of-work all describe one machine. A caller-supplied string replaced only the first two, which made the request contradict itself and measurably cost trust with some sitekeys. You can stop sending it.
proxy string | object optional
URL string (http://user:pass@host:port) or object form with host, port, scheme, username, password. Supported schemes: http, https, socks5, socks5h, socks4 (default http). For example socks5://user:pass@host:port. The solve runs on our servers, so the host must be a public hostname or public IP reachable from the internet. localhost, a private address such as 127.0.0.1 or 10.0.0.5, or a bare service name from your own network (proxy-pool) cannot be reached from here: the request is answered with a 422 validation_error pointing at proxy, or the solve fails.

On enterprise sitekeys, send a sticky proxy: the same exit IP you will submit the token from. Enterprise deployments commonly bind the token to the IP that solved it, so a token solved from one address and submitted from another is refused as invalid-response. We measured this on Discord's enterprise sitekey (2026-08-04): solving through a second residential exit, with everything else in the flow held identical, took token acceptance from 6/11 to 0/12. A rotating proxy breaks the same rule by design, since it can move you to a new exit between the solve and your submit.

Hold the session for the whole exchange: page load, solve, and submit. If you omit proxy entirely the solve egresses from our pool rather than your address, so it cannot match. On an IP-bound sitekey, pass your own.

Query parameters

wait integer optional
Block up to N seconds (1-90); return as soon as the solve reaches a terminal state.

Responses

202
ACCEPTED
pending: Async: solve is still in flight.
200
OK
terminal: Set ?wait and the solve reached a terminal state in time.
422
UNPROCESSABLE
validation_error: Malformed body or invalid field value.
402
PAYMENT REQUIRED
insufficient_credits: Balance < 1 credit.
402
PAYMENT REQUIRED
key_credit_limit_exceeded: Key hit its per-key credit limit. Raise or reset it on the Keys page.
429
TOO MANY
concurrency_limit_exceeded: Account already at its concurrency cap (default 5). Retry once one completes.
429
TOO MANY
sitekey_rate_limited: hCaptcha is rate-limiting requests for this sitekey. Wait the Retry-After seconds; the response body says whether the limit is sitekey-wide or scoped to your egress.

Retrieve a solve

GET https://api.nonecap.com /v1/solves/{id}

Fetch the current state of a solve by id. With ?wait=N (1-90 s), the call blocks until the solve reaches a terminal state or the wait expires.

Path parameters

id string required
Prefixed ULID of the solve, e.g. solve_01HQF7K3JKWZX.

Query parameters

wait integer optional
Block up to N seconds (1-90); return as soon as the solve reaches a terminal state.

Responses

200
OK
Terminal (solved/failed/cancelled/expired). Returns the full solve object.
202
ACCEPTED
Still pending/solving. Poll again (or use ?wait).
404
NOT FOUND
Solve id unknown or purged after the 30-day retention window.

List solves

GET https://api.nonecap.com /v1/solves

Cursor-paginated history of solves for the authenticated account. Newest first.

Query parameters

limit integer optional
Number of solves to return. Default 20, range 1-100.
starting_after string optional
Pagination cursor: id of the last solve from the previous page.
status string optional
Filter by solve status.
pendingsolvingsolvedfailedcancelledexpired
type string optional
Filter by captcha type.
hcaptchahcaptcha_enterprise

Response carries has_more: boolean. There is no separate cursor field. The cursor is the id of the last item in data.

Cancel a solve

DELETE https://api.nonecap.com /v1/solves/{id}

Cancel a pending or solving solve. A cancelled solve is never charged.

Path parameters

id string required
Prefixed ULID of the solve to cancel, e.g. solve_01HQF7K3JKWZX.

Responses

200
OK
Cancelled. Returns the solve with status: "cancelled".
409
CONFLICT
conflict: Solve already reached a terminal state and cannot be cancelled.
404
NOT FOUND
not_found: Unknown solve id.
03 · account

Account & balance

GET https://api.nonecap.com /v1/me

Returns the authenticated account plus the current credit balance. Useful for showing balance in your own dashboard or as a pre-flight before a batch.

Response

object "account" optional
Constant.
id string optional
Prefixed ULID, e.g. user_01HQ….
email string optional
Verified email on the account.
credits_balance integer optional
Available solves remaining.
created_at string · RFC 3339 optional
Account creation timestamp.
04 · feedback

Report feedback

POST https://api.nonecap.com /v1/feedback

Report the tokens your target rejected, so we can see how they actually performed and catch regressions early. Reporting accepted ones as well is optional. This is the batch endpoint: buffer your verdicts and flush up to 500 at once. There's no extra charge and no new key or scope, since it's gated on the same solve scope that created the solves, so your existing API key works unchanged. You can only report your own solves.

Body parameters

feedback array required
1-500 feedback items. An empty, missing, non-array, or over-cap value is a 422 validation_error on feedback. The batch is not all-or-nothing: each item is validated and upserted independently, so one bad solve_id never discards the good rows.

Feedback item

solve_id string required
The solve_... id returned by Create a solve. Must belong to you and be a solved solve. Keep this id next to the token you submit downstream, since it's the only thing you need to retain to report the outcome later.
outcome string · enum required
The downstream verdict. Only accepted and rejected count toward the acceptance rate; unknown (verdict undetermined), unused (token never submitted or expired), and error (a failure that wasn't the token's fault, such as a network problem, a 5xx or maintenance) are recorded but excluded from the quality denominator.
acceptedrejectedunknownunusederror
reason string optional
Freeform downstream reason or code: whatever your target returned when it refused the token, for example invalid-response. Truncated to 512 characters. This is the field worth populating, because it's the only part of the rejection we cannot see from our side.
context string optional
Optional free text: anything about the attempt you think would help us diagnose it. There is no schema, so a status code, the response body your target returned, which proxy pool you were on or what your retry did are all fine. A human reads it when you raise a ticket; nothing parses it. Truncated to 2000 characters.
reported_at string · ISO 8601 optional
Client's instant of the downstream verdict. Advisory only: the server always stamps its own created_at and updated_at.

Rules

  • You can report your own solves only. A foreign or unknown id resolves to a per-item not_eligible.
  • Only a solve that reached solved is reportable; anything else is not_eligible.
  • The first report must land within 30 days of the solve. A late first report is expired_window, but corrections to an already-reported solve are not age-gated.
  • Reports are idempotent and last-write-wins. Reporting the same solve_id again overwrites the mutable fields (status: updated); re-sending an identical value does nothing (status: unchanged). The first reported outcome stays immutable server-side and report_count bumps only on a real change, so verdict flips stay auditable.

Two response shapes

This batch endpoint returns the batch envelope, { object: "feedback_batch", recorded, updated, unchanged, failed, results }, where each results entry has a status of recorded, updated, unchanged or error, and a failed item nests a bare { code, message, param } without the error wrapper. The single-item alias below returns the feedback resource directly, or the standard { error: { code, message, param } } envelope on failure. Handle both.

Responses

200
OK
feedback_batch: Returned once the request is structurally valid, even if some items failed. Inspect the per-item status.
400
BAD REQUEST
invalid_request: Body is not a JSON object.
422
UNPROCESSABLE
validation_error: feedback is missing, empty, not an array, or over 500 items (param feedback).

Per-item error.code values inside a 200 batch are validation_error (bad field), not_eligible (unknown / foreign / not-solved id), or expired_window (first report past the 30-day window).

Report one solve

POST https://api.nonecap.com /v1/solves/:id/feedback

Convenience alias for reporting a single solve. It runs the same validation and upsert path as one Report feedback batch item, and is meant for low-volume callers and interactive testing. The solve_id comes from the path, so the body is just the item fields (no feedback wrapper). High-volume integrations should use the batch endpoint above.

Body parameters

outcome string · enum required
The downstream verdict. Only accepted and rejected count toward the acceptance rate.
acceptedrejectedunknownunusederror
reason string optional
Freeform downstream reason or code, e.g. the rejection message your target returned. Truncated to 512 characters.
context string optional
Optional free text: anything about the attempt you think would help us diagnose it. There is no schema, so a status code, the response body your target returned, which proxy pool you were on or what your retry did are all fine. A human reads it when you raise a ticket; nothing parses it. Truncated to 2000 characters.
reported_at string · ISO 8601 optional
Advisory verdict timestamp; the server stamps its own timestamps.

The rules match the batch endpoint: your own solves only, only solves that reached solved, a first report within 30 days, and idempotent last-write-wins.

Responses

200
OK
feedback: The feedback resource (same shape whether recorded or updated).
400
BAD REQUEST
invalid_request: Body is not JSON.
404
NOT FOUND
not_found: Unknown id, not your solve, or not a solved solve.
422
UNPROCESSABLE
validation_error: Bad field value (an outcome outside the enum).
422
UNPROCESSABLE
expired_window: First report is past the 30-day window (param solve_id).