Integrations

Retool + CaptchaAI: Internal Tool CAPTCHA Form Handling

Retool lets teams build internal tools quickly. When those tools need to interact with CAPTCHA-protected external services — submitting forms, pulling data from portals, or automating third-party workflows — CaptchaAI handles the solving through Retool's REST API queries.

This guide shows how to set up CaptchaAI as a REST API resource in Retool, build queries to submit and retrieve CAPTCHA solutions, and wire them into your app's UI.

Real-World Scenario

Your ops team uses a Retool app to submit data to a government portal that requires reCAPTCHA v2. Instead of manually solving CAPTCHAs, the Retool app:

  1. Takes the sitekey and page URL as input
  2. Submits the CAPTCHA to CaptchaAI
  3. Polls for the result
  4. Displays the solved token for use in the form submission

Step 1: Create CaptchaAI as a REST API Resource

In Retool, navigate to ResourcesCreate NewREST API:

Field Value
Name CaptchaAI
Base URL https://ocr.captchaai.com
Authentication None (API key sent as query parameter)

Save the resource. You'll reference it in queries.

Step 2: Create the Submit Query

Create a new query called submitCaptcha:

Setting Value
Resource CaptchaAI
Action Type GET
URL Path /in.php

Query Parameters:

Key Value
key {{secretsStore.CAPTCHAAI_API_KEY}}
method userrecaptcha
googlekey {{sitekeyInput.value}}
pageurl {{pageurlInput.value}}
json 1

Store your API key in Retool's Secrets Store (Settings → Secrets) to avoid hardcoding it.

Transformer (optional):

// Parse the response
const data = {{ submitCaptcha.data }};
if (data.status === 1) {
  return { taskId: data.request, status: 'submitted' };
}
return { error: data.request, status: 'failed' };

Step 3: Create the Poll Query

Create a query called pollResult:

Setting Value
Resource CaptchaAI
Action Type GET
URL Path /res.php

Query Parameters:

Key Value
key {{secretsStore.CAPTCHAAI_API_KEY}}
action get
id {{submitCaptcha.data.request}}
json 1

Transformer:

const data = {{ pollResult.data }};
if (data.status === 1) {
  return { token: data.request, status: 'solved' };
}
if (data.request === 'CAPCHA_NOT_READY') {
  return { status: 'pending' };
}
return { error: data.request, status: 'error' };

Step 4: Build the Polling Loop with JavaScript

Create a JavaScript query called solveCaptcha that orchestrates the submit and poll cycle:

// solveCaptcha — JavaScript Query
async function solve() {
  // Submit the CAPTCHA task
  await submitCaptcha.trigger();
  const submitResult = submitCaptcha.data;

  if (submitResult.status !== 1) {
    return { error: submitResult.request, status: 'submit_failed' };
  }

  const taskId = submitResult.request;

  // Wait 15 seconds before first poll
  await new Promise(r => setTimeout(r, 15000));

  // Poll up to 20 times (100 seconds max)
  for (let i = 0; i < 20; i++) {
    await pollResult.trigger({
      additionalScope: { taskId: taskId }
    });

    const result = pollResult.data;

    if (result.status === 1) {
      return { token: result.request, status: 'solved' };
    }

    if (result.request !== 'CAPCHA_NOT_READY') {
      return { error: result.request, status: 'error' };
    }

    // Wait 5 seconds before next poll
    await new Promise(r => setTimeout(r, 5000));
  }

  return { error: 'Polling timeout', status: 'timeout' };
}

return solve();

Step 5: Build the UI

Create a simple Retool app layout:

Input Section

  • Text Input (sitekeyInput): Label "reCAPTCHA Sitekey"
  • Text Input (pageurlInput): Label "Page URL"
  • Button (solveButton): Label "Solve CAPTCHA", onClick → solveCaptcha.trigger()

Status Section

  • Text component: {{ solveCaptcha.isFetching ? "Solving..." : "" }}
  • Loading Indicator: Visible when {{ solveCaptcha.isFetching }}

Result Section

  • Text Area (tokenOutput):
  • Value: {{ solveCaptcha.data?.token || '' }}
  • Label: "Solved Token"
  • Read-only
  • Copy Button: Copies token to clipboard
  • Status Badge: Shows success/error based on {{ solveCaptcha.data?.status }}

Step 6: Use the Token in Downstream Queries

Once solved, use the token in another Retool query that submits the form:

Create a query called submitForm:

Setting Value
Resource Your target API
Action Type POST
Body Form data including g-recaptcha-response: {{solveCaptcha.data.token}}

Wire this to a "Submit Form" button that's enabled only when {{ solveCaptcha.data?.status === 'solved' }}.

Troubleshooting

Problem Cause Fix
ERROR_WRONG_USER_KEY API key not in Secrets Store or wrong value Verify key in Settings → Secrets
Query returns raw text instead of JSON Missing json=1 parameter Add json: 1 to query parameters
Polling timeout CAPTCHA type needs longer solve time Increase polling iterations from 20 to 30
submitCaptcha.data is undefined Query hasn't run yet Ensure submit query runs before poll
JavaScript query timeout Retool's 120-second limit for JS queries Reduce polling to 20 iterations with 5-second intervals

FAQ

Can I store the CaptchaAI API key securely in Retool?

Yes. Use Retool's Secrets Store (Environment → Secrets) and reference it as {{secretsStore.CAPTCHAAI_API_KEY}}. This keeps the key out of query definitions visible to app builders.

Does this work with Retool Cloud and Self-hosted?

Yes, both versions support REST API resources and JavaScript queries. Self-hosted Retool can also hit CaptchaAI's API directly without proxy requirements.

Can I build a reusable CAPTCHA-solving module in Retool?

Yes. Create a Retool Module with the submit/poll queries and UI components, then embed it in any app that needs CAPTCHA solving.

What CAPTCHA types can I solve through Retool?

Any type CaptchaAI supports — reCAPTCHA v2/v3, Cloudflare Turnstile, Image CAPTCHAs. Adjust the method and parameters in the submit query accordingly.

Next Steps

Add CAPTCHA solving to your Retool internal tools — get your CaptchaAI API key and set up the REST API resource.

Related guides:

Full Working Code

Complete runnable examples for this article in Python, Node.js, PHP, Go, Java, C#, Ruby, Rust, Kotlin & Bash.

View on GitHub →

Discussions (0)

No comments yet.