Use Playwright APIRequestContext with a CAPTCHA solution

By solvethree4 min read

Use request.newContext() when you already know the application's POST endpoint and form fields. It creates an HTTP client without launching Chromium. You don't need browser field interaction just to submit a solvethree token.

If the application needs cookies, CSRF data or other form fields, supply those too. The SDK solves the CAPTCHA; it doesn't reconstruct the application's login flow.

On this page

Prepare the dependencies and target request

  1. 1.

    Install the dependencies

    Use Node.js 22+ and install the playwright package. Install the official JavaScript SDK from solvethree's version 0.1.0 tarball: solvethree-sdk-0.1.0.tgz, under /downloads/ on solvethree.com. It imports as @solvethree/sdk; don't assume a registry release is available.

  2. 2.

    Set the environment variables

    Provide SOLVETHREE_API_KEY, SOLVETHREE_PROXY and TARGET_URL to the Node.js process. Keep the workspace API key server-side. TARGET_URL must be the actual application POST endpoint, not merely the page displaying the CAPTCHA. Use an HTTP or HTTPS proxy URL without a path, query or fragment.

  3. 3.

    Confirm the site profile and action

    The target hostname needs an active profile available to your workspace. Set it up in the console before solving; passing a hostname doesn't create a profile. The example uses the action login; change it if the target expects another action. The request defaults to reCAPTCHA Enterprise v3.

Have the application's required form fields and session requirements ready before running the example. It sends only the CAPTCHA response field. See site profile setup if the hostname isn't configured yet.

Solve, then submit through the same proxy

Playwright APIRequestContext · no browser required
import { request } from 'playwright';
import { Solvethree } from '@solvethree/sdk';
const client = new Solvethree({ apiKey: process.env.SOLVETHREE_API_KEY });
const target = new URL(process.env.TARGET_URL);
const proxy = new URL(process.env.SOLVETHREE_PROXY);
const result = await client.solve({ site: target.hostname, action: 'login', proxy: process.env.SOLVETHREE_PROXY });
const proxyConfig = { server: `${proxy.protocol}//${proxy.host}`, username: decodeURIComponent(proxy.username), password: decodeURIComponent(proxy.password) };

const targetClient = await request.newContext({
  proxy: proxyConfig, timeout: 30000,
  extraHTTPHeaders: { 'User-Agent': result.solution.userAgent, 'sec-ch-ua': result.solution.secChUa },
  // storageState: 'your-application-session.json', // only if your flow needs it
});
try {
  const response = await targetClient.post(target.href, {
    form: { 'g-recaptcha-response': result.solution.token }, maxRedirects: 0,
  });
  console.log('Application HTTP status:', response.status());
  // Inspect the response and supply the application's actual fields/CSRF/session.
} finally { await targetClient.dispose(); }

client.solve() creates the task once and polls until it's ready or throws. The example uses one proxy for both solving and submission. For Playwright, it separates the proxy server address from its credentials and decodes the username and password.

The request context applies solution.userAgent and solution.secChUa as headers. The form sends solution.token in g-recaptcha-response, not the task ID. Keep the proxy and returned identity together; don't replace them with unrelated browser headers or send the token to a different target.

Extend the form with the application's actual fields, including credentials or CSRF data where required. If you already have the required application session, enable the optional storageState setting and point it to that session's saved state.

APIRequestContext maintains its own cookie jar, so requests made through the same context retain cookies it receives. It doesn't automatically share cookies with an unrelated browser context. Keep any prerequisite session requests and the final POST in the same context when they need to share cookies. The finally block disposes of the context when submission finishes.

Check the application response before solving again

The example logs the application HTTP status; it doesn't establish that login succeeded. With maxRedirects set to zero, you see the initial response rather than following a redirect. Inspect its body, headers and cookies against the application's expected success response. A redirect or HTTP 200 alone may not tell you whether the operation succeeded.

The task is ready, but the application rejects the POST.

Inspect the outgoing request first: token field, hostname and action, proxy, returned identity, required form fields, CSRF value and session cookies.

Try this: Correct the missing or mismatched request data before paying for another solve. A ready task provides a usable solution, not a guarantee of downstream acceptance.

The solve ends with a failed task.

Read errorCode and error; don't treat every failure as a credit problem.

Try this: Stop polling that task and address the reported cause. Failed solves are refunded. For error interpretation, use API error handling.

Task creation times out or the connection drops.

Determine whether you have a task ID or used a requestId. A connection timeout doesn't prove creation failed.

Try this: Retrieve the existing task when you have its ID. For recoverable creation requests, supply a requestId and reuse it with exactly the same inputs; that returns the existing task. Don't immediately start another logical solve.

The application request returns an HTTP error.

Inspect the target response separately from the solvethree task result.

Try this: Handle the non-success response before treating its body as a success payload. Fix the application request or reported server error rather than assuming the token caused it.

Run your first task.

Start with $5 in workspace credit.

Create a workspace