01 ·getting started

The VanishCaptcha 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.vanishcaptcha.com/v1
Auth scheme
Bearer token
Content type
application/json
Captcha
hCaptcha

The challenge is played in a real browser rather than classified from pixels. An image classifier produces a token hCaptcha's own scoring distrusts; this produces one that passes its server-side checks, because it came from the widget.

All endpoints live under https://api.vanishcaptcha.com. HTTPS only, JSON in and out, and every solve request carries authentication. A successful solve costs 10 credits plus 4 per challenge round after the first, capped at 40; failures, timeouts and cancellations are never charged.

This is a learning and test project rather than a commercial service. There is no uptime commitment behind it — see measured status.

Quickstart

  1. Create a key in the dashboard. It starts nc_live_. You can view and copy it there again. If it leaks, rotate it — the old one stops working immediately. Get a key.
  2. POST /v1/solves with the sitekey and page URL. Add ?wait=N (1–90 seconds) to hold the request open until the solve reaches a terminal state.
  3. If the wait window closes first you get 202 with a status of pending or solving and no token. Poll GET /v1/solves/{id} — also with ?wait= — until the status is terminal.
  4. Put the returned P1_ token in the target form's h-captcha-response field and submit it. Tokens are short-lived; submit promptly.

A solved solve costs 10 credits, plus 4 for every challenge round after the first, never more than 40. hCaptcha decides the rounds, so the charge varies with the sitekey — the rounds and credits_charged on every solve say what you actually paid. A failed solve is never charged, so retries cost nothing but time.

Authentication

Every request carries Authorization: Bearer <key>. There are no unauthenticated solve endpoints and no API keys in query strings.

Keys are independent of each other: each has its own optional spend limit, so one leaked key cannot drain the balance. Revoking a key takes effect on the next request.

A missing, malformed, unknown or revoked key is the same 401 with code invalid_key — the API does not distinguish them, because the difference is only useful to someone guessing.

Errors

Every error is a JSON object with an error key holding a stable code and a human message. Branch on the code; the message is for your logs and may be reworded.

Note that a solve can fail without the HTTP request failing. A 200 carrying status: "failed" and an error object is the normal way a challenge that could not be beaten comes back.

Codes

invalid_key401
Missing, unknown or revoked API key.
rate_limited429
Too many requests, or too many solves in flight for this account.
insufficient_credits402
The account behind this key cannot pay for a solve. Unlike a rate limit this does not clear by waiting — top up and the same key starts working again, with no redeploy.
invalid_sitekey400
The sitekey is malformed, or the page did not present it.
challenge_timeoutsolve
No challenge arrived, or it expired before it could be answered.
unsupported_challengesolve
A challenge family we cannot play yet. The message names it. Not charged.
session_lostsolve
The browser session died mid-solve. Safe to retry.
proxy_blockedsolve
The target refused the connection before a challenge appeared.
solve_failedsolve
The challenge was played and hCaptcha rejected the answer.
not_found404
Unknown solve id, or one whose record has expired.
expiredsolve
The solve outlived its window before reaching a terminal state.
cancelledsolve
The solve was cancelled before it finished.

Rate limits

Limits are on solves in flight rather than requests per second — the expensive thing here is a browser holding a widget open, not an HTTP round trip.

Exceeding it is a 429 with code rate_limited. Back off and retry; nothing is charged for a refused request. If you need more concurrency, open a ticket from the dashboard rather than retrying harder.

A per-key credit limit, if you set one, is enforced the same way: once a key has spent its allowance it stops solving while the rest of the account continues.

Pagination

The one list endpoint is cursor-paginated. The cursor is the id of the last item you received; pass it as starting_after on the next request. There is no separate cursor field to track.

An id rather than an offset because the index grows at the head: with an offset, a solve finishing between two pages would shift everything down and you would see a row twice or miss one entirely.

limitinteger 1–100
How many to return. Default 20.
starting_afterstring
A solve_ id from the previous page, exclusive. An id that is not in this account's history is a 400 rather than a silent restart from the top — that would hand back a page you had already seen and look like the end of the list.
has_moreboolean
On the response. With a filter applied it means "there is more history to walk", not "there are more matches" — the search is bounded per request, so keep paging until it is false.

Versioning

The version is in the path. /v1 will not gain a required field or change the meaning of an existing one; new optional request fields and new response fields may appear, so parse leniently and ignore what you do not recognise.

New status values and new error codes are the two things most likely to show up. Treat an unknown status as non-terminal and an unknown error code as a generic failure rather than crashing on it.

The OpenAPI document is the contract this page describes.

02 ·solves

The solve object

A solve is created, worked, and reaches one terminal state. Its record is kept long enough to be read back and then expires.

idstring
Always prefixed solve_. Use it to poll and to reference the solve in support.
statusenum
One of pending, solving, solved, failed, cancelled, expired. The first two are non-terminal; the rest are final.
tokenstring
The hCaptcha token. Present only when status is solved — treat its absence as the signal, not an error.
errorobject
Present when the status is failed, expired or cancelled. Carries the same code and message shape as an HTTP error.

Create a solve

POSThttps://api.vanishcaptcha.com/v1/solves

Queues a challenge and, with ?wait=, holds the request open until it finishes.

Body

typestringrequired
hcaptcha or hcaptcha_enterprise. Enterprise sitekeys need rqdata as well.
sitekeystringrequired
The sitekey from the target page's hCaptcha iframe.
urlstringrequired
The page the challenge appears on. Used to load the widget in the same context the token will be spent in.
user_agentstringrequired
The user agent your client will submit the token with. A mismatch here is a common cause of a token that passes hCaptcha and is then refused by the target.
rqdatastringoptional
Enterprise passthrough. Required for hcaptcha_enterprise, ignored otherwise.

Query

waitinteger 1–90optional
Seconds to hold the request open. Omit it and the call returns immediately with 202. Values outside the range are clamped.

Responses

202
Accepted
pending / solving: the solve is still in flight. Poll GET /v1/solves/{id}.
200
OK
Terminal: ?wait was set and the solve reached a final state in time.
401
Unauthorized
Missing or unknown key. Code invalid_key.
429
Too Many Requests
Too many solves in flight. Code rate_limited. Nothing charged.

Retrieve a solve

GEThttps://api.vanishcaptcha.com/v1/solves/{id}

Reads one solve back. Takes the same ?wait= parameter, so polling is a long-poll rather than a busy loop — call it with wait=30 and it returns the moment the solve is terminal.

A solve belongs to the account that created it. Another account's id returns 404, the same answer as an id that was never issued.

Query

waitinteger 1–90optional
Seconds to hold the request open waiting for a terminal state. Omit for an immediate read.

List solves

GEThttps://api.vanishcaptcha.com/v1/solves

Solve history for the authenticated key's account, newest first. Keys on the same account share one history — this is the account's solves, not the key's.

List rows carry no token. A page of fifty tokens is fifty chances to leak one into a log, so the token stays on retrieve, one at a time. Rows also omit the sitekey, url and user agent you sent.

Query

limitinteger 1–100
Default 20.
starting_afterstring
Cursor — see Pagination.
statusstring
Filter to one of pending, solving, solved, failed, cancelled, expired.
typestring
Filter to hcaptcha or hcaptcha_enterprise.

History is kept for 365 days. Only the list is durable: the solve record itself expires, so GET /v1/solves/{id} stops answering for a solve long before it leaves this list, and a listed solve never carries its token.

Cancel a solve

DELETEhttps://api.vanishcaptcha.com/v1/solves/{id}

Stops a solve that has not finished. A cancelled solve is never charged.

What cancel actually guarantees. A pending solve is genuinely stopped: it leaves the queue and no widget is ever opened for it. A solve already solving has a browser holding a live widget — we mark it cancelled and hand the concurrency slot back, but we do not interrupt the worker, because reaching into a session mid-play is how you strand it and waste the challenge it was given. So a solve cancelled at that moment may still finish and report solved. You are not charged either way.

A solve that already reached a terminal state is a 409 with code conflict, not a silent re-marking — rewriting a delivered token's record as cancelled would erase it from your own history.

03 ·account

Account & balance

GEThttps://api.vanishcaptcha.com/v1/me

The account behind this key and what it has left to spend. Useful as a pre-flight before a batch, or to show a balance in your own dashboard.

credits_balanceinteger
Credits available now.
email_verifiedboolean
Whether the address on the account has been confirmed.

A key issued before accounts existed has no account to describe and gets a 404 here. That is not a problem with the key — it still solves.

04 ·feedback

Report feedback

POSThttps://api.vanishcaptcha.com/v1/feedback

A token can pass hCaptcha and still be refused by your target. This is the only way we learn that — nothing else in the system can see what happened after you submitted it. Reporting accepted tokens too is optional and sharpens the read.

Not all-or-nothing. Each item is validated and upserted on its own, so one stale id in a flush of four hundred does not throw away the other three hundred and ninety-nine. Read results per row. A batch over the cap is refused whole, though — silently truncating would drop verdicts you believe you sent.

Reporting is idempotent and last-write-wins: retrying a flush cannot leave two verdicts on one solve, and a token reported unknown and later rejected is a rejection.

Body

feedbackarrayrequired
1–500 items. Empty, missing, not an array, or over the cap is a 422 with code validation_error.

Feedback item

solve_idstringrequired
Must be your own solve, must have reached solved, and must be within 30 days. Keep this id next to the token you submit downstream — it is the only thing you need to retain to report the outcome later.
outcomeenumrequired
accepted, rejected, unknown, unused or error. Only the first two count toward an acceptance rate; the rest are recorded and excluded, because "I never submitted it" is not evidence about the token.
reasonstring
The rejection message your target returned. Truncated to 512 chars.
contextstring
Free text — a status code, a response body, which proxy pool you were on. No schema; a human reads it when you raise a ticket. Truncated to 2000 chars.
reported_atstring
Advisory. The server stamps its own timestamp regardless.

Report one solve

POSThttps://api.vanishcaptcha.com/v1/solves/{id}/feedback

The same validation and the same upsert as one batch item, for low-volume callers and interactive testing. The id comes from the path, so the body is just the item fields with no feedback wrapper.

One item means a per-item failure is the whole request's failure: an unknown or not-yours solve is 404, anything else invalid is 422. The batch endpoint reports those as rows instead.

05 ·service

Service status

GEThttps://api.vanishcaptcha.com/v1/status

Public and unauthenticated — the one endpoint someone reads when they cannot sign in. Ninety days of recorded uptime per component, sampled every ten seconds rather than probed on read.

A day with uptime: null is a day nobody recorded, not a day that was down. The basis field says what the number was measured against: clock for the API itself, because the minutes it was down are the minutes nothing was sampling, and observed for everything it watches.

The status page renders exactly this, including active_incidents and a 90-day history. Incidents are opened after three consecutive failed samples (~30 seconds) and closed automatically when the component recovers. There is no mailing-list subscribe on this endpoint.

Status metrics

GEThttps://api.vanishcaptcha.com/v1/status/metrics

Public. Rolling 24 hours in five-minute buckets. response_time is HTTP latency of the API itself (not solve duration) — long-polls with wait= and the status page's own traffic are excluded. availability is the fraction of status samples in each bucket where every component was up.

delta_ms_vs_7d is this window's p50 minus the p50 of the previous six days. A positive number means the API is slower than last week.

Health checks

GEThttps://api.vanishcaptcha.com/healthz · /readyz

/healthz says the process is answering. /readyz says its Redis is reachable and returns 503 when it is not — the one to point a load balancer at.

Both are unauthenticated and outside /v1, because a health check that needs a key stops working exactly when you need it.