
Lucas Mitchell
Automation Engineer

pageAction, and score policy from a server-side registry rather than from model-generated text.The safest way to solve reCAPTCHA v3 in CrewAI is to expose CapSolver through a typed, policy-controlled tool. CrewAI should decide when the task needs verification, but trusted application code should resolve the approved target, site key, page action, proxy mode, and score policy. CapSolver's reCAPTCHA v3 documentation defines the supported task types and parameters, while the user-provided Agent SDK maps structured tool calls to capsolver-core. The tool should submit the token server-side, verify the resulting page state, and give the crew a small result such as verified, review_required, or stopped. This design prevents URL drift, action guessing, secret leakage, duplicate solves, and false success signals.
reCAPTCHA v3 is score-based and usually runs without a visible checkbox. The target application invokes an action, receives a token, and evaluates that token on the server. A CrewAI workflow can therefore fail even when it never sees a visual challenge.
The common causes are operational rather than conversational:
pageAction did not match the page's runtime action;The CapSolver reCAPTCHA blog contains supporting implementation guides, while the AI and automation FAQ helps define safe agent boundaries.
A multi-agent crew works best when responsibility is explicit.
| Role | Allowed responsibility | Must not control |
|---|---|---|
| Navigator | Observe the approved application state | API key, proxy credential, raw token |
| Verification planner | Decide whether the approved step needs the tool | Arbitrary URL or site key |
| CapSolver tool | Resolve policy, create one task, submit token | Unbounded retries or unrelated browsing |
| State validator | Confirm expected route and semantic markers | Business approval decisions |
| Reviewer | Inspect redacted evidence on failure | Secret session material |
The model can choose a registered target ID. It should not construct the target URL or challenge parameters.
CrewAI's custom-tool documentation supports BaseTool with a Pydantic args_schema, the @tool decorator, typed results, and asynchronous tools for I/O-bound operations.
The user-provided CapSolver Agent documentation explains that capsolver-agent wraps capsolver-core: create_executor() creates the executor, and executor.execute("solve_captcha", args) dispatches the typed request to the core engine.
Install the documented packages:
pip install git+https://github.com/capsolver-ai/capsolver-core.git
pip install git+https://github.com/capsolver-ai/capsolver-agent.git
pip install crewai
export CAPSOLVER_API_KEY="CAP-..."
Keep keys in the runtime secret store. Do not place them in crew prompts, task descriptions, or tool results.
from dataclasses import dataclass
@dataclass(frozen=True)
class RecaptchaV3Policy:
target_id: str
website_url: str
website_key: str
page_action: str
minimum_score: float
enterprise: bool
proxy_profile: str | None
allowed_crew_role: str
max_attempts: int = 1
TARGETS = {
"approved_login_test": RecaptchaV3Policy(
target_id="approved_login_test",
website_url="https://approved.example.com/login",
website_key="PUBLIC_SITE_KEY",
page_action="login",
minimum_score=0.7,
enterprise=False,
proxy_profile=None,
allowed_crew_role="verification_specialist",
)
}
The public site key is not an account secret, but it should still come from a trusted configuration so the model cannot redirect the tool.
pageAction Instead of Guessing ItCapSolver's reCAPTCHA v3 documentation lists pageAction as an optional task field and explains that the value can be found in the page's grecaptcha.execute call.
@dataclass(frozen=True)
class PageObservation:
target_id: str
current_url: str
observed_action: str
observed_site_key: str
form_state: str
observed_at: str
def validate_observation(
observation: PageObservation,
policy: RecaptchaV3Policy,
) -> None:
if observation.current_url != policy.website_url:
raise PermissionError("Observed URL does not match policy")
if observation.observed_site_key != policy.website_key:
raise ValueError("Observed site key does not match policy")
if observation.observed_action != policy.page_action:
raise ValueError("Observed page action does not match policy")
if observation.form_state != "READY_FOR_VERIFICATION":
raise ValueError("Application state is not ready")
The tool should reject mismatches rather than create a token for uncertain parameters.
| Parameter | Purpose | Policy rule |
|---|---|---|
captcha_type |
Selects reCAPTCHA v3 in the Agent SDK | Fixed to reCaptchaV3 |
website_url |
Page hosting the challenge | Loaded from registry |
website_key |
Public site key | Loaded from registry and checked against page |
page_action |
Runtime v3 action | Must match observation |
min_score |
Requested minimum score | Set by target policy |
enterprise |
Standard or Enterprise path | Fixed by integration configuration |
proxy |
Optional network identity | Resolved from a secret profile if required |
CapSolver documents Standard, Enterprise, proxy, and proxyless task variants. Its result can contain gRecaptchaResponse, user-agent data, and session values when the relevant mode is enabled.
The CapSolver products page helps confirm the supported task family before implementation.
import os
from typing import Literal
from crewai.tools import tool
from pydantic import BaseModel, Field
from capsolver_agent.schema import create_executor
executor = create_executor(api_key=os.environ["CAPSOLVER_API_KEY"])
class SolveRequest(BaseModel):
target_id: str = Field(description="Registered target identifier")
crew_role: str = Field(description="Role requesting verification")
observed_action: str = Field(description="Action observed on the live page")
observed_site_key: str = Field(description="Site key observed on the live page")
state_id: str = Field(description="Opaque server-side application state identifier")
class SolveResult(BaseModel):
status: Literal["verified", "review_required", "stopped"]
target_id: str
state_id: str
reason: str
task_attempted: bool
The result model deliberately excludes the token, API key, proxy, cookies, and raw provider response.
PROXY_VAULT = {
"approved_proxy": os.environ.get("APPROVED_PROXY")
}
async def submit_token_and_verify(
*,
state_id: str,
token: str,
policy: RecaptchaV3Policy,
) -> bool:
"""Application-owned submit-and-check function."""
response = await application_sessions.submit_recaptcha_v3(
state_id=state_id,
token=token,
expected_action=policy.page_action,
)
return (
response.current_url.startswith("https://approved.example.com/account")
and response.semantic_marker == "AUTHENTICATED_ACCOUNT_PAGE"
and response.challenge_present is False
)
application_sessions represents your authorized browser or HTTP session service. The solver tool uses it, but the model does not receive its credentials.
@tool("Solve approved reCAPTCHA v3", result_schema=SolveResult)
async def solve_approved_recaptcha_v3(
target_id: str,
crew_role: str,
observed_action: str,
observed_site_key: str,
state_id: str,
) -> dict:
"""Solve one registered reCAPTCHA v3 step and verify application state."""
policy = TARGETS.get(target_id)
if policy is None:
return SolveResult(
status="stopped",
target_id=target_id,
state_id=state_id,
reason="Unknown target",
task_attempted=False,
).model_dump()
if crew_role != policy.allowed_crew_role:
return SolveResult(
status="stopped",
target_id=target_id,
state_id=state_id,
reason="Role is not permitted to call this tool",
task_attempted=False,
).model_dump()
if observed_action != policy.page_action:
return SolveResult(
status="review_required",
target_id=target_id,
state_id=state_id,
reason="Observed action does not match target policy",
task_attempted=False,
).model_dump()
if observed_site_key != policy.website_key:
return SolveResult(
status="review_required",
target_id=target_id,
state_id=state_id,
reason="Observed site key does not match target policy",
task_attempted=False,
).model_dump()
args = {
"captcha_type": "reCaptchaV3",
"website_url": policy.website_url,
"website_key": policy.website_key,
"page_action": policy.page_action,
"min_score": policy.minimum_score,
"enterprise": policy.enterprise,
}
if policy.proxy_profile:
args["proxy"] = PROXY_VAULT[policy.proxy_profile]
result = await executor.execute("solve_captcha", args)
if not result.get("success"):
return SolveResult(
status="review_required",
target_id=target_id,
state_id=state_id,
reason="CapSolver task did not complete",
task_attempted=True,
).model_dump()
solution = result.get("solution") or {}
token = solution.get("token")
if not token:
return SolveResult(
status="review_required",
target_id=target_id,
state_id=state_id,
reason="Task result did not contain a token",
task_attempted=True,
).model_dump()
verified = await submit_token_and_verify(
state_id=state_id,
token=token,
policy=policy,
)
return SolveResult(
status="verified" if verified else "review_required",
target_id=target_id,
state_id=state_id,
reason="Application state verified" if verified else "Application state not verified",
task_attempted=True,
).model_dump()
This implementation keeps the credential inside trusted code and gives the crew only a policy-safe status.
from crewai import Agent, Crew, Process, Task
verification_agent = Agent(
role="verification_specialist",
goal="Complete only registered verification steps and report verified state",
backstory=(
"You operate approved verification tools. You never invent target IDs, "
"site keys, actions, credentials, or success states."
),
tools=[solve_approved_recaptcha_v3],
allow_delegation=False,
verbose=True,
)
verification_task = Task(
description=(
"For the registered target in the supplied observation, call the tool once "
"only if the URL, site key, action, and state are confirmed. Return the "
"structured status without secrets."
),
expected_output="A structured verified, review_required, or stopped result.",
agent=verification_agent,
)
crew = Crew(
agents=[verification_agent],
tasks=[verification_task],
process=Process.sequential,
verbose=True,
)
Do not attach the solver tool to every agent. Limiting it to one role makes authorization and auditing clearer.
from datetime import datetime, timedelta, timezone
ATTEMPTS: dict[tuple[str, str], datetime] = {}
def claim_attempt(target_id: str, state_id: str) -> bool:
key = (target_id, state_id)
now = datetime.now(timezone.utc)
prior = ATTEMPTS.get(key)
if prior and now - prior < timedelta(minutes=2):
return False
ATTEMPTS[key] = now
return True
Call claim_attempt() before executor.execute(). A repeated crew message should not create a second token for the same application state.
CrewAI memory, traces, and verbose logs may preserve tool output. Return only:
{
"status": "verified",
"target_id": "approved_login_test",
"state_id": "state_7c19",
"reason": "Application state verified",
"task_attempted": true
}
Never return the token, CapSolver API key, proxy value, browser cookie, raw HTML, password, or personal form data.
The CapSolver errors and troubleshooting FAQ can support provider-error classification without exposing the raw response to the crew.
The validator should require several independent signals:
@dataclass(frozen=True)
class StateCheck:
expected_path_prefix: str
required_marker: str
forbidden_markers: tuple[str, ...]
def is_verified(page, check: StateCheck) -> bool:
return (
page.url.path.startswith(check.expected_path_prefix)
and page.has_semantic_marker(check.required_marker)
and not any(page.contains(marker) for marker in check.forbidden_markers)
and page.http_status == 200
)
A changed URL alone is not enough. Require the expected route, semantic marker, status, and absence of known challenge or error states.
CapSolver documents Enterprise variants and optional session mode. Do not let the crew infer these options.
@dataclass(frozen=True)
class RecaptchaV3Policy:
target_id: str
website_url: str
website_key: str
page_action: str
minimum_score: float
enterprise: bool
is_session: bool
proxy_profile: str | None
allowed_crew_role: str
max_attempts: int = 1
If the approved target uses Enterprise, store that fact in policy. If session mode is needed, handle returned session values inside the application session service and keep them out of crew output.
| Design | Parameter integrity | Secret safety | Page-state assurance | Recommendation |
|---|---|---|---|---|
| Model supplies URL, key, and action | Low | Low | Low | Avoid |
| Tool returns token to crew | Medium | Low | Low | Avoid |
| Registry-resolved tool submits and verifies | High | High | High | Preferred |
| Human-only verification step | High | High | High | Use for sensitive or uncertain states |
The preferred design gives the model decision authority but keeps execution authority inside trusted code.
Track redacted fields such as:
SAFE_FIELDS = {
"target_id",
"crew_role",
"action_match",
"task_attempted",
"provider_category",
"duration_ms",
"verified",
"review_reason",
}
def safe_event(event: dict) -> dict:
return {key: event[key] for key in SAFE_FIELDS if key in event}
The CapSolver status page can help distinguish provider availability from application-specific failures.
import pytest
@pytest.mark.asyncio
async def test_unknown_target_stops_before_task():
result = await solve_approved_recaptcha_v3.run(
target_id="unknown",
crew_role="verification_specialist",
observed_action="login",
observed_site_key="x",
state_id="state-1",
)
assert result["status"] == "stopped"
assert result["task_attempted"] is False
@pytest.mark.asyncio
async def test_action_mismatch_requests_review():
result = await solve_approved_recaptcha_v3.run(
target_id="approved_login_test",
crew_role="verification_specialist",
observed_action="checkout",
observed_site_key="PUBLIC_SITE_KEY",
state_id="state-2",
)
assert result["status"] == "review_required"
assert result["task_attempted"] is False
Also test duplicate-attempt blocking, secret redaction, missing token handling, Enterprise policy, and page-verification failure.
Bonus Code: Use code WEBS at CapSolver Dashboard to get an extra 5% bonus on every recharge.
The CapSolver CAPTCHA-solving FAQ provides additional task-lifecycle guidance.
Use a CrewAI reCAPTCHA v3 solver only on sites you own, test, or have explicit permission to automate. Respect terms, rate limits, authentication boundaries, privacy obligations, and internal access policies. A public site key does not grant permission to access a protected workflow. Keep consequential submissions, payments, account changes, and sensitive data decisions behind separate approval controls.
A production CrewAI reCAPTCHA v3 solver should be narrow, typed, and policy-controlled. CrewAI can identify the need for verification, but trusted code must resolve the target, site key, page action, score, Enterprise mode, and network settings. CapSolver should run once for a validated state, the token should be submitted server-side, and the crew should receive only a redacted result after the intended page is verified.
Start an authorized implementation with CapSolver, test it on a controlled page, and add parameter, duplicate-call, redaction, and page-state tests before production use.
CrewAI can call a typed tool that delegates to the documented CapSolver Agent executor. Keep target resolution, secrets, token submission, and verification inside trusted application code.
The target URL and site key are required. The page action, minimum score, Enterprise setting, session mode, and proxy depend on the approved target configuration.
No. Observe the action from the live approved page and compare it with a server-side policy value.
No. Submit it inside trusted code and return only a redacted verified, review-required, or stopped status.
Use one attempt per observed application state by default. A second attempt should require a new observation and an explicit policy decision.
CloakBrowser review for 2026 covering features, pricing, licensing, deployment options, advantages, limitations, alternatives, and ideal use cases.

Integrate CloakBrowser with CapSolver through Playwright-compatible Python automation, including reCAPTCHA v2, image tasks, code, and troubleshooting.
