reCAPTCHA v2, v3 and Enterprise: What Actually Differs
The three are not difficulty tiers. v2 is an interactive challenge that mints a token you verify pass/fail. v3 is invisible, always mints a token, and attaches a risk score that only the site owner ever sees. Enterprise is v3's model behind different keys and a different server-side verification API. Most integration bugs come from treating them as the same thing with a version number attached.
Which one is on the page? Read the loader URL.
Look at the reCAPTCHA script tag and the call site.
api.jswith norenderparam plus adiv.g-recaptcha— v2 checkbox. An image challenge may follow the click.- The same loader with
data-size="invisible", orgrecaptcha.execute()against a v2 key — v2 invisible. No checkbox. api.js?render=<sitekey>plusgrecaptcha.execute(sitekey, {action: 'login'})— v3. Invisible, always mints, carries a score.enterprise.js?render=<sitekey>plusgrecaptcha.enterprise.execute(...)— Enterprise. Score-based or checkbox-based, depending on how the key was made.
That maps onto KagedCap's task field, the only selector on POST /solve:
- v3 —
ReCaptchaV3Task/ReCaptchaV3TaskProxyLess - v3 Enterprise —
ReCaptchaV3EnterpriseTask/ReCaptchaV3EnterpriseTaskProxyLess - v2 invisible —
ReCaptchaV2Task/ReCaptchaV2TaskProxyLess - v2 checkbox — not supported
A *TaskProxyLess task must not carry a proxy; every other reCAPTCHA task must. Both rules are schema validation, so neither returns a dedicated proxy error code — you get 400 validation_error with an issues array naming the field.
{
"success": false,
"error": "validation_error",
"issues": [{ "path": "proxy", "message": "ProxyLess tasks must not include a proxy" }]
}ProxyLess also sits behind a switch we can turn off when our shared egress is under pressure, answering 503 proxyless_disabled. A request carrying your own proxy never sees it.
A v3 score is the site's risk assessment, not a pass/fail
The token is opaque. The score is not inside it.
Your token goes to the site's backend, the backend exchanges it with Google using its secret key, and Google's reply carries success, score, action, hostname and challenge_ts. The score is computed at verification time and disclosed only to whoever holds that secret — nobody on the client side, a solver included, can read it out of a token.
In the KagedCap response, score is populated only when a siteverify call ran, which normally means you supplied your own secretKey. Send a secret only for a key you own; it is redacted before anything is logged.
The verification block is derived from siteverify, not a passthrough. Four fields:
score— the number, on a scoring keysuccess— the booleanaction— the action Google echoed backerrors— Google'serror-codes, renamed
hostname and challenge_ts are not carried through; if you gate on either, run your own siteverify.
The async endpoint (POST /v2/solve plus poll) carries no score and no verification at all — both are synchronous /solve only.
What `action` does, and why a mismatched action weakens a token
action is a free-form label the page passes to grecaptcha.execute(). It rides along with the token and Google echoes it back on verification. Google tells site owners to check that it matches the endpoint being called.
So a mismatch costs you twice.
Hard failure. A backend that verifies the action rejects a token minted under a different one, whatever the score is. You get a clean 200 from the solver and a rejection downstream — miserable to debug if you did not know to look.
Soft degradation. reCAPTCHA's risk analysis is scoped per action, so a token carrying an action the endpoint has never seen is a weaker signal.
Read the action out of the page's own execute() call and send that exact string. It is case-sensitive. Google restricts the character set — alphanumerics, slashes and underscores — and says action names must not be user-specific; if you see something that looks like an ID, re-read the page. Check their current list before writing a sanitiser: stripping a legal character breaks the binding.
On KagedCap, action is optional on every reCAPTCHA task. Omit it and you get a genuine no-action solve, which is valid and sometimes correct.
v2 invisible has no score and no action at all
v2 answers one question: did this token verify. siteverify returns success as a boolean, plus hostname and challenge_ts. There is no score field and no action field. KagedCap returns score: null on ReCaptchaV2Task and ReCaptchaV2TaskProxyLess, always — not "null until we can compute it," null because the number does not exist.
There is also nothing for an action to bind to: v2's verification reply has no action field, so nothing checks it.
v2 checkbox is deliberately out of scope. The checkbox path can escalate to an image challenge, which is a vision problem rather than a token problem, and a partial answer there would be worse than no answer. If your target is a checkbox widget that shows pictures of crosswalks, this API is not what you want, and no request shape will change that.
Enterprise: different keys, different verification, same request shape
Enterprise keys are created in Google Cloud rather than the classic reCAPTCHA admin console, and the page loads enterprise.js. From the minting side almost nothing changes — a site key, an action, a token — which is why the KagedCap request body is identical apart from the task name.
What changes is the site's half. Enterprise verification runs through the reCAPTCHA Enterprise API's assessment call instead of siteverify, which returns risk reasons alongside the score and lets the site annotate an assessment afterwards.
Two practical notes. An Enterprise key can be score-based or checkbox-based, and only the score-based flavour is covered here. And KagedCap's verification block is built from siteverify whatever the task, so on an Enterprise key treat it as a convenience for classic verification and gate on your own assessment call.
Otherwise the two behave identically: same fields, same ProxyLess rule, same error shapes, separately priced.
When a solver is the wrong answer
You control the site. Then do not solve your own captcha. Allowlist your test infrastructure's egress, use Google's published test key pair outside production, or put a bypass behind a secret your CI holds — staging-only, never a path that exists in production. A solver running against your own staging environment means something in your test setup was never wired up properly. It is slower, it costs money, and it tests the captcha rather than your application.
You do not control the site and you are not authorized. Then stop here. The challenge is in the path because someone decided who gets through, and that is a permission question, not an engineering one. No API fixes it.
The case this is actually for is narrower and real: an agent doing sanctioned work — accessibility tooling, synthetic monitoring of a checkout you own, a regression suite against a property you operate, data access you have a contract for — running into a control aimed at abusive traffic. If you cannot name who authorized the access, you do not have the case.
Last, the limits that apply to every variant. Google documents a two-minute token lifetime; mint at the moment you submit, never batch ahead. Tokens are single-use and bound to the site key and the site's own domain. Send a real desktop userAgent that matches what you replay with — the token embeds it. And on the async endpoint, results are cleared roughly five minutes after completion, so a late poll returns done with a token of null rather than something you can use.
Common questions
v2 is an interactive challenge — a checkbox, sometimes an image puzzle — and the token it produces verifies pass or fail with no score. v3 is invisible, always produces a token, and attaches a risk score from 0.0 to 1.0 that Google returns only to the site owner during server-side verification. v3 also carries an action label that binds the token to a specific page context; v2 has no action and no score.
Not from the token alone. The score is not encoded in the token — Google computes and returns it during server-side verification, and only to whoever holds the site's secret key. A solver can report a score only if it ran that verification call, which normally means you supplied your own secret. On KagedCap the score field is populated only when a siteverify call ran, and the verification block that comes back is derived from siteverify rather than copied from it: it carries score, success, action and errors, and does not carry hostname or challenge_ts.
There is no universal answer, because the threshold belongs to the site, not to Google. Google's guidance is to start near 0.5 and tune from there, and real sites run different thresholds per action — a login gate and a comment form on the same site often disagree. Many sites do not use the score as a hard gate at all, feeding it into a broader risk decision such as step-up authentication or a review queue.
The action is a label passed to grecaptcha.execute() that travels with the token, and Google echoes it back in the verification response. Google tells site owners to check that it matches the endpoint being called, so a token minted under the wrong action can be rejected outright regardless of its score. reCAPTCHA's risk analysis is also scoped per action, so a token carrying an action the endpoint has never seen is a weaker signal. Copy the action string from the page's own execute() call exactly — it is case-sensitive. On KagedCap the action field is optional on every reCAPTCHA task.
Enterprise is not a harder version of the same challenge — it is the same score-based model issued under Google Cloud keys with a different server-side API. The page loads enterprise.js instead of api.js, and the site verifies with an assessment call rather than siteverify, which returns risk reasons and lets the site annotate outcomes so the model learns from its own fraud labels. The token mint itself takes the same inputs: a site key and an action.
Read the loader URL. api.js with no render parameter and a div.g-recaptcha is v2 checkbox; the same loader with data-size set to invisible is v2 invisible; api.js with a render parameter plus a grecaptcha.execute call carrying an action is v3; and enterprise.js with a render parameter plus grecaptcha.enterprise.execute is Enterprise. The site key alone does not tell you the version.
The request is rejected before any solve is attempted. Both proxy rules are request-schema validation, so you get a 400 whose error code is validation_error, with an issues array naming the proxy field and the reason. A missing proxy on a non-ProxyLess reCAPTCHA task is rejected the same way. Branch on validation_error and read the issues array rather than on a proxy-specific error code.