Интеграция
Как подключить API, чтобы ответы приходили быстро, а интеграция не ломалась на ошибках.
На этой странице — правила, от которых зависит скорость и надёжность: когда опрашивать результат, как держать соединение, что делать с каждой ошибкой. Если интеграцию пишет ИИ-ассистент, дайте ему эту страницу целиком — в конце есть готовое задание для него.
Как проходит задача
POST /createпринимает капчу и сразу возвращает идентификатор задачи.POST /resultпо этому идентификатору возвращает ответ илиCAPCHA_NOT_READY, если задача ещё решается.
Сколько ждать ответа, зависит от капчи:
| Капча | Когда готов ответ |
|---|---|
| Большинство | меньше чем за секунду |
| Сложные | несколько секунд |
Поэтому главное правило — первый опрос через 1 секунду после /create:
к этому моменту ответ обычно уже готов, а пауза длиннее — потеря времени на
каждой задаче.
Что отправлять
Тело /create — JSON с полем type и полями своего типа капчи. Остальное —
опрос, соединение, ошибки — у всех типов одинаковое.
| Капча | type | Поля /create | Ответ /result |
|---|---|---|---|
| Силуэты | SmartCaptcha | click, task — картинки | координаты точек в порядке клика: coordinates:x=..,y=..;... |
| Калейдоскоп, версия 1 | PazlCaptcha | task — HTML страницы | номер шага, на котором пазл сложен |
| Калейдоскоп, версия 2 | PazlCaptchaV2 | image — картинка, task — список перестановок | номер шага, на котором пазл сложен |
| Текстовая | TextCaptcha | task — картинка | текст с картинки |
Картинки и HTML передаются в Base64. У всех типов есть необязательное поле
app_id — идентификатор приложения разработчика.
График опроса
| Шаг | Когда | Зачем |
|---|---|---|
| 1 | через 1 с после ответа /create | забрать ответ — обычно он уже готов |
| 2 | дальше каждые 2 с | дождаться решения сложной капчи |
| — | после 90 с без ответа | прекратить опрос, взять у сайта новую капчу |
Опрашивать чаще, чем раз в 2 секунды, не нужно: сложные капчи решаются
секундами, и частый опрос их не ускорит. Опрос заканчивается, как только
пришёл status: 1 или любой ответ, кроме CAPCHA_NOT_READY.
Каждую принятую задачу опрашивайте до конца — не бросайте её на середине.
Соединение
- Одно соединение на все запросы. Создайте HTTP-клиент один раз и
переиспользуйте его:
requests.Sessionв Python, общийfetchили agent в Node, один дескриптор cURL в PHP, одинHttpClientв C#, одинhttp.Clientв Go. Новое TLS-соединение на каждый запрос — это лишние десятки миллисекунд на каждом шаге опроса. - HTTP/2 поддерживается — можно отправлять много запросов параллельно по одному соединению.
- Сервер закрывает простаивающее соединение примерно через 10 секунд. Клиент должен уметь открыть его заново: почти все HTTP-библиотеки делают это сами, если не держать сокет вручную.
- Таймауты: подключение — 5 с, ответ на
/create— 15 с, на/result— 10 с. Без таймаутов зависший запрос остановит всю очередь задач.
Запрос
- Ключ — только в заголовке
X-API-Key. - Тело — JSON с заголовком
Content-Type: application/json. - Картинки — исходные байты в Base64, без префикса
data:image/.... Не пересжимайте и не перерисовывайте их: пересжатие теряет детали, и капча решается дольше и хуже. - Форматы — PNG или JPEG, до 2 МБ на картинку. Если JPEG отвергается с
ERROR_IMAGE_TYPE_NOT_SUPPORTED, отправьте его в PNG.
Ошибки и повторы
| Ответ | Что делать |
|---|---|
CAPCHA_NOT_READY | продолжить опрос по графику |
CAPCHA_NOT_AVAILABLE на /result | капча не решена — взять новую и создать новую задачу |
ERROR_NO_SUCH_TASK_ID | задачи нет или она устарела — создать новую |
TOO_MANY_REQUESTS, SERVER_ERROR, ERROR_SERVER, ERROR_NO_ACTIVE_SERVER на /create, обрыв сети, таймаут | повторить тот же запрос через 1, 2, 4 с; после трёх неудач — остановиться |
ERROR_TASK_LOOP_DETECTED | перестать отправлять эту капчу — взять у сайта новую |
ERROR_ZERO_BALANCE | остановить отправку до пополнения баланса |
ERROR_KEY_DOES_NOT_EXIST, ERROR_IP_NOT_ALLOWED, ERROR_USER_DISABLED | остановиться: это настройка, а не сбой |
ошибки картинок (ERROR_IMAGE_…, ERROR_BODY_…, ERROR_EMPTY_BODY, ERROR_DUPLICATE_DATA) | исправить запрос, повторять без изменений бессмысленно |
Полный список — в кодах ошибок.
Повтор /result после обрыва сети безопасен: для одной задачи он всегда
возвращает один и тот же ответ.
Не отправляйте одну и ту же капчу по кругу. Если сайт не принял ответ,
возьмите у него новую капчу. Десять одинаковых задач подряд с одного адреса без
успешного результата блокируют эту капчу на 15 минут
(ERROR_TASK_LOOP_DETECTED). А если ответ оказался неверным — отметьте
задачу кнопкой «Неверный ответ» в личном кабинете, на странице
«Задачи за сегодня»: так капча быстрее перестанет
решаться неправильно.
Чек-лист
- Ключ в заголовке
X-API-Key, тело — JSON. - Картинки отправляются исходными байтами, без пересжатия.
- Первый
/result— через 1 с после/create. - Дальше — каждые 2 с, не дольше 90 с.
- Один HTTP-клиент на все запросы, с таймаутами.
- Сетевые ошибки и
TOO_MANY_REQUESTSповторяются с паузой, остальные ошибки — по таблице. - Одна и та же капча не отправляется по кругу.
Пример
import base64
import time
import requests
API = 'https://api.capsola.cloud'
# Один клиент на все запросы: соединение переиспользуется.
session = requests.Session()
session.headers['X-API-Key'] = 'API_KEY'
# Опрос: через 1 с, дальше каждые 2 с — всего около 90 с.
POLL_DELAYS = [1] + [2] * 45
RETRYABLE = {'TOO_MANY_REQUESTS', 'SERVER_ERROR', 'ERROR_SERVER'}
# ERROR_NO_ACTIVE_SERVER на /create — все решатели заняты, повторяем, как SERVER_ERROR.
# На /result он значит «капча не решена», и повтор не поможет.
RETRYABLE_CREATE = RETRYABLE | {'ERROR_NO_ACTIVE_SERVER'}
def call(path, payload, timeout):
"""Запрос с повтором при сетевых сбоях: паузы 1, 2, 4 с."""
retryable = RETRYABLE_CREATE if path == '/create' else RETRYABLE
for pause in (1, 2, 4, None):
try:
answer = session.post(f'{API}{path}', json=payload, timeout=(5, timeout)).json()
if answer.get('response') not in retryable:
return answer
except requests.RequestException:
pass
if pause is None:
raise RuntimeError(f'{path}: сервис не отвечает')
time.sleep(pause)
def solve(payload):
created = call('/create', payload, 15)
if created['status'] != 1:
raise RuntimeError(created['response'])
for delay in POLL_DELAYS:
time.sleep(delay)
result = call('/result', {'id': created['response']}, 10)
if result['status'] == 1:
return result['response']
if result['response'] != 'CAPCHA_NOT_READY':
raise RuntimeError(result['response'])
raise TimeoutError('нет ответа за 90 секунд')
def b64(url):
return base64.b64encode(requests.get(url, timeout=15).content).decode()
# Тело /create зависит от типа капчи — см. «Что отправлять».
print(solve({
'type': 'SmartCaptcha',
'click': b64('https://capsola.cloud/examples/smart/click.png'),
'task': b64('https://capsola.cloud/examples/smart/task.png'),
}))
// Node 18+: встроенный fetch сам переиспользует соединение.
const API = "https://api.capsola.cloud";
const HEADERS = { "Content-Type": "application/json", "X-API-Key": "API_KEY" };
// Опрос: через 1 с, дальше каждые 2 с — всего около 90 с.
const POLL_DELAYS = [1000, ...Array(45).fill(2000)];
const RETRYABLE = new Set(["TOO_MANY_REQUESTS", "SERVER_ERROR", "ERROR_SERVER"]);
// ERROR_NO_ACTIVE_SERVER на /create — все решатели заняты, повторяем, как SERVER_ERROR.
// На /result он значит «капча не решена», и повтор не поможет.
const RETRYABLE_CREATE = new Set([...RETRYABLE, "ERROR_NO_ACTIVE_SERVER"]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Запрос с повтором при сетевых сбоях: паузы 1, 2, 4 с.
async function call(path, payload, timeoutMs) {
const retryable = path === "/create" ? RETRYABLE_CREATE : RETRYABLE;
for (const pause of [1000, 2000, 4000, null]) {
try {
const res = await fetch(API + path, {
method: "POST",
headers: HEADERS,
body: JSON.stringify(payload),
signal: AbortSignal.timeout(timeoutMs),
});
const answer = await res.json();
if (!retryable.has(answer.response)) return answer;
} catch {
// сеть или таймаут — повторим
}
if (pause === null) throw new Error(`${path}: сервис не отвечает`);
await sleep(pause);
}
}
async function solve(payload) {
const created = await call("/create", payload, 15000);
if (created.status !== 1) throw new Error(created.response);
for (const delay of POLL_DELAYS) {
await sleep(delay);
const result = await call("/result", { id: created.response }, 10000);
if (result.status === 1) return result.response;
if (result.response !== "CAPCHA_NOT_READY") throw new Error(result.response);
}
throw new Error("нет ответа за 90 секунд");
}
async function b64(url) {
return Buffer.from(await (await fetch(url)).arrayBuffer()).toString("base64");
}
async function main() {
// Тело /create зависит от типа капчи — см. «Что отправлять».
const answer = await solve({
type: "SmartCaptcha",
click: await b64("https://capsola.cloud/examples/smart/click.png"),
task: await b64("https://capsola.cloud/examples/smart/task.png"),
});
console.log(answer);
}
main().catch(console.error);
<?php
// Один дескриптор cURL на все запросы: соединение переиспользуется.
$ch = curl_init();
function call($ch, string $path, array $payload, int $timeout): array
{
// Повтор при сетевых сбоях: паузы 1, 2, 4 с.
$retryable = ['TOO_MANY_REQUESTS', 'SERVER_ERROR', 'ERROR_SERVER'];
// ERROR_NO_ACTIVE_SERVER на /create — все решатели заняты, повторяем, как SERVER_ERROR.
// На /result он значит «капча не решена», и повтор не поможет.
if ($path === '/create') {
$retryable[] = 'ERROR_NO_ACTIVE_SERVER';
}
foreach ([1, 2, 4, null] as $pause) {
curl_setopt_array($ch, [
CURLOPT_URL => 'https://api.capsola.cloud' . $path,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-API-Key: API_KEY'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => $timeout,
]);
$answer = json_decode((string) curl_exec($ch), true);
if (is_array($answer) && !in_array($answer['response'] ?? '', $retryable, true)) {
return $answer;
}
if ($pause === null) {
throw new RuntimeException("$path: сервис не отвечает");
}
sleep($pause);
}
}
function solve($ch, array $payload): string
{
$created = call($ch, '/create', $payload, 15);
if ($created['status'] !== 1) {
throw new RuntimeException($created['response']);
}
// Опрос: через 1 с, дальше каждые 2 с — всего около 90 с.
$delays = array_merge([1], array_fill(0, 45, 2));
foreach ($delays as $delay) {
usleep((int) ($delay * 1000000));
$result = call($ch, '/result', ['id' => $created['response']], 10);
if ($result['status'] === 1) {
return $result['response'];
}
if ($result['response'] !== 'CAPCHA_NOT_READY') {
throw new RuntimeException($result['response']);
}
}
throw new RuntimeException('нет ответа за 90 секунд');
}
// Тело /create зависит от типа капчи — см. «Что отправлять».
echo solve($ch, [
'type' => 'SmartCaptcha',
'click' => base64_encode(file_get_contents('https://capsola.cloud/examples/smart/click.png')),
'task' => base64_encode(file_get_contents('https://capsola.cloud/examples/smart/task.png')),
]), PHP_EOL;
// .NET 6+, консольное приложение.
using System.Net.Http.Json;
using System.Text.Json;
const string Api = "https://api.capsola.cloud";
string[] retryable = { "TOO_MANY_REQUESTS", "SERVER_ERROR", "ERROR_SERVER" };
// ERROR_NO_ACTIVE_SERVER на /create — все решатели заняты, повторяем, как SERVER_ERROR.
// На /result он значит «капча не решена», и повтор не поможет.
var retryableCreate = retryable.Append("ERROR_NO_ACTIVE_SERVER").ToArray();
// Один HttpClient на всё приложение: соединение переиспользуется.
var http = new HttpClient(new SocketsHttpHandler { ConnectTimeout = TimeSpan.FromSeconds(5) });
http.DefaultRequestHeaders.Add("X-API-Key", "API_KEY");
// Запрос с повтором при сетевых сбоях: паузы 1, 2, 4 с.
async Task<JsonElement> Call(string path, object payload, int timeoutSeconds)
{
var retry = path == "/create" ? retryableCreate : retryable;
for (var attempt = 0; ; attempt++)
{
try
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutSeconds));
using var res = await http.PostAsJsonAsync(Api + path, payload, cts.Token);
var answer = await res.Content.ReadFromJsonAsync<JsonElement>(cancellationToken: cts.Token);
if (!retry.Contains(answer.GetProperty("response").GetString())) return answer;
}
catch (Exception e) when (e is HttpRequestException or TaskCanceledException or JsonException) { }
if (attempt == 3) throw new Exception($"{path}: сервис не отвечает");
await Task.Delay(TimeSpan.FromSeconds(1 << attempt));
}
}
async Task<string> Solve(object payload)
{
var created = await Call("/create", payload, 15);
var id = created.GetProperty("response").GetString()!;
if (created.GetProperty("status").GetInt32() != 1) throw new Exception(id);
// Опрос: через 1 с, дальше каждые 2 с — всего около 90 с.
var delays = new[] { 1000 }.Concat(Enumerable.Repeat(2000, 45));
foreach (var delay in delays)
{
await Task.Delay(delay);
var result = await Call("/result", new { id }, 10);
var response = result.GetProperty("response").GetString()!;
if (result.GetProperty("status").GetInt32() == 1) return response;
if (response != "CAPCHA_NOT_READY") throw new Exception(response);
}
throw new TimeoutException("нет ответа за 90 секунд");
}
async Task<string> B64(string url) => Convert.ToBase64String(await http.GetByteArrayAsync(url));
// Тело /create зависит от типа капчи — см. «Что отправлять».
Console.WriteLine(await Solve(new
{
type = "SmartCaptcha",
click = await B64("https://capsola.cloud/examples/smart/click.png"),
task = await B64("https://capsola.cloud/examples/smart/task.png"),
}));
package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"time"
)
const (
api = "https://api.capsola.cloud"
apiKey = "API_KEY"
)
// Один клиент на все запросы: соединение переиспользуется.
var client = newClient()
func newClient() *http.Client {
transport := http.DefaultTransport.(*http.Transport).Clone()
transport.DialContext = (&net.Dialer{Timeout: 5 * time.Second}).DialContext
return &http.Client{Transport: transport}
}
var retryable = map[string]bool{"TOO_MANY_REQUESTS": true, "SERVER_ERROR": true, "ERROR_SERVER": true}
// ERROR_NO_ACTIVE_SERVER на /create — все решатели заняты, повторяем, как SERVER_ERROR.
// На /result он значит «капча не решена», и повтор не поможет.
func shouldRetry(path, response string) bool {
return retryable[response] || (path == "/create" && response == "ERROR_NO_ACTIVE_SERVER")
}
type answer struct {
Status int `json:"status"`
Response string `json:"response"`
}
func post(path string, body []byte, timeout time.Duration) (answer, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodPost, api+path, bytes.NewReader(body))
if err != nil {
return answer{}, err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", apiKey)
res, err := client.Do(req)
if err != nil {
return answer{}, err
}
defer res.Body.Close()
var a answer
err = json.NewDecoder(res.Body).Decode(&a)
return a, err
}
// Запрос с повтором при сетевых сбоях: паузы 1, 2, 4 с.
func call(path string, payload any, timeout time.Duration) (answer, error) {
body, err := json.Marshal(payload)
if err != nil {
return answer{}, err
}
for attempt := 0; ; attempt++ {
a, err := post(path, body, timeout)
if err == nil && !shouldRetry(path, a.Response) {
return a, nil
}
if attempt == 3 {
return answer{}, fmt.Errorf("%s: сервис не отвечает", path)
}
time.Sleep(time.Second << attempt)
}
}
func solve(payload map[string]string) (string, error) {
created, err := call("/create", payload, 15*time.Second)
if err != nil {
return "", err
}
if created.Status != 1 {
return "", errors.New(created.Response)
}
// Опрос: через 1 с, дальше каждые 2 с — всего около 90 с.
delays := []time.Duration{time.Second}
for i := 0; i < 45; i++ {
delays = append(delays, 2*time.Second)
}
for _, delay := range delays {
time.Sleep(delay)
result, err := call("/result", map[string]string{"id": created.Response}, 10*time.Second)
if err != nil {
return "", err
}
if result.Status == 1 {
return result.Response, nil
}
if result.Response != "CAPCHA_NOT_READY" {
return "", errors.New(result.Response)
}
}
return "", errors.New("нет ответа за 90 секунд")
}
func b64(url string) string {
res, err := client.Get(url)
if err != nil {
panic(err)
}
defer res.Body.Close()
data, err := io.ReadAll(res.Body)
if err != nil {
panic(err)
}
return base64.StdEncoding.EncodeToString(data)
}
func main() {
// Тело /create зависит от типа капчи — см. «Что отправлять».
answer, err := solve(map[string]string{
"type": "SmartCaptcha",
"click": b64("https://capsola.cloud/examples/smart/click.png"),
"task": b64("https://capsola.cloud/examples/smart/task.png"),
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(answer)
}
Задание для ИИ-ассистента
Если интеграцию пишет ИИ, скопируйте ему этот текст — вместе со ссылкой на
документацию одним файлом: /llms-full.txt.
Подключи Capsola API для решения капчи. Документация целиком:
https://front.capsola.space/llms-full.txt
Прежде чем писать код, спроси меня, какую капчу нужно решать, если я
не указал это сам:
- SmartCaptcha — Силуэты, клик по иконкам;
- PazlCaptcha или PazlCaptchaV2 — Калейдоскоп, сдвиг слайдера;
- TextCaptcha — Текстовая, текст с картинки.
Поля запроса и формат ответа возьми со страницы этого типа.
Обязательные правила:
1. POST https://api.capsola.cloud/create, тело JSON с полем "type" и
полями выбранного типа, ключ — в заголовке X-API-Key. Картинки —
исходные байты в Base64, без пересжатия и без префикса data:.
2. Первый POST /result {"id": "<id>"} — через 1 с после /create.
Если CAPCHA_NOT_READY — дальше каждые 2 с,
не дольше 90 с. Чаще раза в 2 секунды не опрашивать.
3. Один HTTP-клиент на все запросы (keep-alive), таймауты: подключение 5 с,
/create 15 с, /result 10 с. Клиент должен переоткрывать соединение,
закрытое сервером после ~10 с простоя.
4. TOO_MANY_REQUESTS, SERVER_ERROR, ERROR_SERVER, ERROR_NO_ACTIVE_SERVER
на /create, сетевые ошибки — повтор того же запроса через 1, 2, 4 с,
потом остановка.
CAPCHA_NOT_AVAILABLE на /result — новая капча и новая задача.
ERROR_TASK_LOOP_DETECTED — не отправлять эту капчу снова.
Ошибки ключа, IP, баланса и картинок — не повторять.
5. Ответ /result зависит от типа: SmartCaptcha — строка
"coordinates:x=..,y=..;..." (точки в порядке клика), PazlCaptcha и
PazlCaptchaV2 — номер шага, TextCaptcha — текст с картинки.
6. Логировать id задачи, время /create и время получения ответа.