Ruby developers building web scrapers, automation tools, or testing frameworks hit CAPTCHAs constantly. Whether you use Rails, Sinatra, or standalone scripts, CaptchaAI's HTTP API integrates cleanly with Ruby's standard library and popular HTTP gems.
This guide covers reCAPTCHA v2, Cloudflare Turnstile, and image CAPTCHA solving using net/http, Faraday, and HTTParty — with production-ready classes you can drop into any Ruby project.
Why Ruby for CAPTCHA Automation
Ruby's clean syntax and rich gem ecosystem make it ideal for automation scripts. Combined with CaptchaAI's REST API, you get:
- Simple HTTP integration —
net/httpships with Ruby, no extra dependencies - Gem flexibility — Faraday, HTTParty, or RestClient all work
- Concurrent solving — Ruby threads handle parallel CAPTCHA requests well
- Rails/Sinatra compatibility — embed solving into web applications directly
Prerequisites
- Ruby 2.7+ (3.x recommended)
- CaptchaAI API key (get one here)
- Target site URL and sitekey for token CAPTCHAs
Install optional gems:
gem install faraday
gem install httparty
gem install nokogiri # for sitekey extraction
CaptchaAI API Flow
Every CAPTCHA solve follows two steps:
- Submit — POST task to
https://ocr.captchaai.com/in.php→ receive task ID - Poll — GET
https://ocr.captchaai.com/res.php?action=get&id=TASK_ID→ receive token
Method 1: net/http (Standard Library)
Zero dependencies — works with any Ruby installation.
Basic reCAPTCHA v2 Solver
require 'net/http'
require 'uri'
require 'json'
class CaptchaSolver
BASE_URL = 'https://ocr.captchaai.com'
POLL_INTERVAL = 5
MAX_ATTEMPTS = 60
def initialize(api_key)
@api_key = api_key
end
# Submit reCAPTCHA v2 task
def solve_recaptcha_v2(site_url, sitekey)
task_id = submit_task(
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: site_url
)
poll_result(task_id)
end
# Submit Cloudflare Turnstile task
def solve_turnstile(site_url, sitekey)
task_id = submit_task(
method: 'turnstile',
key: sitekey,
pageurl: site_url
)
poll_result(task_id)
end
# Submit image CAPTCHA (base64)
def solve_image(image_base64)
task_id = submit_task(
method: 'base64',
body: image_base64
)
poll_result(task_id)
end
private
def submit_task(params)
uri = URI("#{BASE_URL}/in.php")
payload = { key: @api_key, json: 1 }.merge(params)
response = Net::HTTP.post_form(uri, payload)
data = JSON.parse(response.body)
raise "Submit failed: #{data['request']}" unless data['status'] == 1
data['request']
end
def poll_result(task_id)
uri = URI("#{BASE_URL}/res.php")
params = { key: @api_key, action: 'get', id: task_id, json: 1 }
MAX_ATTEMPTS.times do
sleep(POLL_INTERVAL)
uri.query = URI.encode_www_form(params)
response = Net::HTTP.get_response(uri)
data = JSON.parse(response.body)
next if data['request'] == 'CAPCHA_NOT_READY'
raise "Solve failed: #{data['request']}" unless data['status'] == 1
return data['request']
end
raise 'Timeout: CAPTCHA not solved within time limit'
end
end
# Usage
solver = CaptchaSolver.new('YOUR_API_KEY')
token = solver.solve_recaptcha_v2(
'https://example.com/login',
'6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-'
)
puts "reCAPTCHA token: #{token[0..50]}..."
Submitting the Token
require 'net/http'
uri = URI('https://example.com/login')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request.set_form_data(
'username' => 'user@example.com',
'password' => 'password',
'g-recaptcha-response' => token
)
response = http.request(request)
puts "Login response: #{response.code}"
Method 2: Faraday (Recommended for Production)
Faraday provides middleware, retries, and connection pooling.
require 'faraday'
require 'json'
class FaradayCaptchaSolver
BASE_URL = 'https://ocr.captchaai.com'
def initialize(api_key)
@api_key = api_key
@conn = Faraday.new(url: BASE_URL) do |f|
f.request :url_encoded
f.response :json, content_type: /\bjson$/
f.adapter Faraday.default_adapter
f.options.timeout = 10
f.options.open_timeout = 5
end
end
def solve_recaptcha_v2(site_url, sitekey)
task_id = submit(
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: site_url
)
poll(task_id)
end
def solve_turnstile(site_url, sitekey)
task_id = submit(
method: 'turnstile',
key: sitekey,
pageurl: site_url
)
poll(task_id)
end
def solve_recaptcha_v3(site_url, sitekey, action: 'verify', min_score: 0.7)
task_id = submit(
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: site_url,
version: 'v3',
action: action,
min_score: min_score
)
poll(task_id)
end
def check_balance
response = @conn.get('/res.php', key: @api_key, action: 'getbalance', json: 1)
response.body['request'].to_f
end
private
def submit(params)
response = @conn.post('/in.php', { key: @api_key, json: 1 }.merge(params))
data = response.body
unless data['status'] == 1
raise "Submit error: #{data['request']}"
end
data['request']
end
def poll(task_id, max_wait: 300, interval: 5)
deadline = Time.now + max_wait
loop do
raise 'Timeout waiting for CAPTCHA solution' if Time.now > deadline
sleep(interval)
response = @conn.get('/res.php', key: @api_key, action: 'get', id: task_id, json: 1)
data = response.body
next if data['request'] == 'CAPCHA_NOT_READY'
raise "Solve error: #{data['request']}" unless data['status'] == 1
return data['request']
end
end
end
# Usage
solver = FaradayCaptchaSolver.new('YOUR_API_KEY')
balance = solver.check_balance
puts "Balance: $#{balance}"
token = solver.solve_recaptcha_v2(
'https://example.com/form',
'6LdKlZEpAAAAAAOQjzC2v_d36tWxCl6dWsozdSy9'
)
puts "Token: #{token[0..50]}..."
Method 3: HTTParty (Simplest Syntax)
HTTParty is ideal for quick scripts.
require 'httparty'
class HTTPartySolver
include HTTParty
base_uri 'https://ocr.captchaai.com'
def initialize(api_key)
@api_key = api_key
end
def solve_recaptcha_v2(site_url, sitekey)
response = self.class.post('/in.php', body: {
key: @api_key,
method: 'userrecaptcha',
googlekey: sitekey,
pageurl: site_url,
json: 1
})
data = response.parsed_response
raise "Submit failed: #{data['request']}" unless data['status'] == 1
poll_result(data['request'])
end
def solve_image(image_path)
image_data = Base64.strict_encode64(File.read(image_path, mode: 'rb'))
response = self.class.post('/in.php', body: {
key: @api_key,
method: 'base64',
body: image_data,
json: 1
})
data = response.parsed_response
raise "Submit failed: #{data['request']}" unless data['status'] == 1
poll_result(data['request'])
end
private
def poll_result(task_id)
60.times do
sleep 5
response = self.class.get('/res.php', query: {
key: @api_key,
action: 'get',
id: task_id,
json: 1
})
data = response.parsed_response
next if data['request'] == 'CAPCHA_NOT_READY'
raise "Solve failed: #{data['request']}" unless data['status'] == 1
return data['request']
end
raise 'CAPTCHA solve timeout'
end
end
# Usage
solver = HTTPartySolver.new('YOUR_API_KEY')
token = solver.solve_recaptcha_v2('https://example.com', 'SITEKEY_HERE')
puts token
Sitekey Extraction with Nokogiri
require 'nokogiri'
require 'open-uri'
def extract_sitekey(url)
doc = Nokogiri::HTML(URI.open(url))
# reCAPTCHA
recaptcha = doc.at_css('[data-sitekey]')
return { type: 'recaptcha', key: recaptcha['data-sitekey'] } if recaptcha
# Turnstile
turnstile = doc.at_css('.cf-turnstile[data-sitekey]')
return { type: 'turnstile', key: turnstile['data-sitekey'] } if turnstile
# Check scripts for sitekey patterns
doc.css('script').each do |script|
text = script.text
if (match = text.match(/sitekey['":\s]+['"]([A-Za-z0-9_-]{20,})['"]/))
return { type: 'unknown', key: match[1] }
end
end
nil
end
result = extract_sitekey('https://example.com/protected')
puts "Found #{result[:type]} sitekey: #{result[:key]}" if result
Concurrent Solving with Threads
Solve multiple CAPTCHAs in parallel using Ruby threads:
require 'thread'
solver = CaptchaSolver.new('YOUR_API_KEY')
tasks = [
{ url: 'https://site-a.com', key: 'SITEKEY_A' },
{ url: 'https://site-b.com', key: 'SITEKEY_B' },
{ url: 'https://site-c.com', key: 'SITEKEY_C' }
]
mutex = Mutex.new
results = {}
threads = tasks.map do |task|
Thread.new do
begin
token = solver.solve_recaptcha_v2(task[:url], task[:key])
mutex.synchronize { results[task[:url]] = token }
rescue => e
mutex.synchronize { results[task[:url]] = "Error: #{e.message}" }
end
end
end
threads.each(&:join)
results.each do |url, result|
puts "#{url}: #{result[0..50]}..."
end
Error Handling Best Practices
class RobustSolver
MAX_RETRIES = 3
RETRY_ERRORS = %w[
ERROR_NO_SLOT_AVAILABLE
ERROR_CAPTCHA_UNSOLVABLE
CAPCHA_NOT_READY
]
def initialize(api_key)
@solver = CaptchaSolver.new(api_key)
end
def solve_with_retry(site_url, sitekey, type: :recaptcha_v2)
retries = 0
begin
case type
when :recaptcha_v2
@solver.solve_recaptcha_v2(site_url, sitekey)
when :turnstile
@solver.solve_turnstile(site_url, sitekey)
end
rescue => e
retries += 1
if retries <= MAX_RETRIES && retryable?(e.message)
delay = 2**retries + rand(0..2)
puts "Retry #{retries}/#{MAX_RETRIES} after #{delay}s: #{e.message}"
sleep(delay)
retry
end
raise
end
end
private
def retryable?(message)
RETRY_ERRORS.any? { |err| message.include?(err) }
end
end
Rails Integration Example
# app/services/captcha_service.rb
class CaptchaService
def initialize
@api_key = Rails.application.credentials.captchaai_api_key
@solver = FaradayCaptchaSolver.new(@api_key)
end
def verify_and_solve(site_url, sitekey)
Rails.cache.fetch("captcha:#{sitekey}", expires_in: 90.seconds) do
@solver.solve_recaptcha_v2(site_url, sitekey)
end
end
end
# In a controller
class ScrapingController < ApplicationController
def create
service = CaptchaService.new
token = service.verify_and_solve(params[:url], params[:sitekey])
render json: { token: token }
rescue => e
render json: { error: e.message }, status: :unprocessable_entity
end
end
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
ERROR_WRONG_USER_KEY |
Invalid API key | Verify key at dashboard |
ERROR_ZERO_BALANCE |
No funds | Top up account |
ERROR_NO_SLOT_AVAILABLE |
Server busy | Retry after 5 seconds |
ERROR_CAPTCHA_UNSOLVABLE |
Failed solving | Retry with fresh task |
SSL_connect error |
Ruby SSL config | Update ca-certificates or set OpenSSL::SSL::VERIFY_PEER |
Timeout::Error |
Network/polling timeout | Increase timeout, check connectivity |
FAQ
Does CaptchaAI have a Ruby gem?
CaptchaAI provides a REST API that works with any Ruby HTTP library. No official gem is needed — net/http, Faraday, or HTTParty all integrate cleanly.
Which Ruby HTTP library should I use?
Use net/http for zero-dependency scripts, Faraday for production apps needing middleware and retries, or HTTParty for quick prototypes.
Can I use this with Ruby on Rails?
Yes. Wrap the solver in a service object and optionally use Rails.cache to avoid re-solving within the token validity window.
Is Ruby threading safe for parallel solves?
Yes. CaptchaAI API calls are I/O-bound, so Ruby threads handle concurrency well. Use a Mutex for shared state.
Related Guides
- Solving CAPTCHAs with PHP
- Solving CAPTCHAs with Go
- Solving CAPTCHAs with Perl
- CaptchaAI API Documentation
Start solving CAPTCHAs in Ruby — get your API key and integrate in minutes.
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.