{
  "openapi": "3.1.0",
  "info": {
    "title": "Legibl \u2014 Anti-Captcha compatible",
    "version": "2026-08-06",
    "description": "The Legibl task API.\n\nEvery endpoint is `POST`, takes JSON, and answers HTTP 200 \u2014 including for\nerrors. Failure is carried in the body as `errorId: 1` with an `errorCode`,\nbecause that is what the clients in this market expect and several of them\nraise on a non-2xx before reading it.\n\nNo 4xx responses are documented because none are returned on these paths. A\nlinter will flag that; the alternative is documenting a status this API does\nnot produce.\n\n## Supported task types\n\nFour, and the list is short on purpose: a type that is claimed but not\nimplemented costs a caller a production incident to discover.\n\n| Captcha | Internal id | Accepted `type` values | Solution key |\n| --- | --- | --- | --- |\n| reCAPTCHA v2 | `recaptcha_v2` | `RecaptchaV2Task, RecaptchaV2TaskProxyless` | `gRecaptchaResponse` |\n| reCAPTCHA v2 invisible | `recaptcha_v2_invisible` | `RecaptchaV2TaskProxyless with isInvisible` | `gRecaptchaResponse` |\n| Cloudflare Turnstile | `turnstile` | `TurnstileTask, TurnstileTaskProxyless, AntiTurnstileTaskProxyLess` | `token` |\n| Image OCR | `image_ocr` | `ImageToTextTask` | `text` |\n\nAnything else \u2014 hCaptcha, DataDome, Kasada, PerimeterX / HUMAN,\nFunCaptcha / Arkose, reCAPTCHA v3, GeeTest, AntiGate \u2014 is refused with\n`ERROR_TASK_NOT_SUPPORTED`. There is no detection and no task type for any of\nthem.\n\n## Sandbox\n\nThe production gateway is live: solves reach a vendor and are charged. A gateway\nrunning in sandbox mode instead answers every solve with a token prefixed\n`sandbox:`, costs nothing, and never reaches a vendor; `GET /ready` reports which\nmode you are talking to. A sandbox token is deliberately not shaped like a real\none, so it cannot be submitted to a target and recorded as a vendor\nfailure.\n\nThis document describes the same gateway as the native one, reached on the Anti-Captcha hostname. Porting an existing Anti-Captcha integration is a base-URL change: the paths, the request bodies and the error envelope are unchanged.\n\nEndpoints Anti-Captcha publishes and this gateway does not implement \u2014 `getQueueStats`, `getSpendingStats`, `getAppStats`, `createTaskSmee`, `pushAntiGateVariable` \u2014 answer HTTP 200 with an `ERROR_TASK_NOT_SUPPORTED` envelope on this hostname rather than 404, because several client libraries raise on a non-2xx before reading the body.",
    "contact": {
      "name": "Legibl support",
      "url": "https://app.legibl.io/support"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://app.legibl.io/acceptable-use"
    }
  },
  "servers": [
    {
      "url": "https://anticaptcha-api.legibl.io"
    }
  ],
  "security": [],
  "paths": {
    "/createTask": {
      "post": {
        "operationId": "createTask",
        "summary": "Submit a captcha for solving",
        "description": "Credit is held before the task is dispatched, not charged after it resolves. Charging only on completion would let concurrent requests each read a balance none of the others had debited yet.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTaskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/CreateTaskResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/getTaskResult": {
      "post": {
        "operationId": "getTaskResult",
        "summary": "Poll for a result",
        "description": "Reads state; it never waits on the vendor. A caller polling for a result is not blocked by the solve it is polling for. Poll every few seconds.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GetTaskResultRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GetTaskResultResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/getBalance": {
      "post": {
        "operationId": "getBalance",
        "summary": "Read the account balance",
        "description": "",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientKeyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/GetBalanceResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/reportCorrect": {
      "post": {
        "operationId": "reportCorrect",
        "summary": "Report that a target accepted the solution",
        "description": "Acceptance is the only signal that distinguishes a working backend from one returning well-formed rubbish. It is not inferred, because a solve the vendor calls successful can still be refused by the target.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ReportResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/reportIncorrect": {
      "post": {
        "operationId": "reportIncorrect",
        "summary": "Report that a target rejected the solution",
        "description": "",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ReportResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/reportCorrectRecaptcha": {
      "post": {
        "operationId": "reportCorrectRecaptcha",
        "summary": "Alias of /reportCorrect",
        "description": "Anti-Captcha appends the captcha type and real integrations call it by that name. A client reporting into a 404 loses the signal silently.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ReportResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/reportIncorrectRecaptcha": {
      "post": {
        "operationId": "reportIncorrectRecaptcha",
        "summary": "Alias of /reportIncorrect",
        "description": "",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ReportResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/reportCorrectImageCaptcha": {
      "post": {
        "operationId": "reportCorrectImageCaptcha",
        "summary": "Alias of /reportCorrect",
        "description": "",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ReportResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/reportIncorrectImageCaptcha": {
      "post": {
        "operationId": "reportIncorrectImageCaptcha",
        "summary": "Alias of /reportIncorrect",
        "description": "",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReportRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Always 200. Failure is carried in the body.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ReportResponse"
                    },
                    {
                      "$ref": "#/components/schemas/ErrorEnvelope"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ClientKeyRequest": {
        "type": "object",
        "required": [
          "clientKey"
        ],
        "properties": {
          "clientKey": {
            "type": "string",
            "description": "Your API key.",
            "examples": [
              "lgbl_..."
            ]
          }
        }
      },
      "Task": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "One of the accepted values in the table above.",
            "examples": [
              "RecaptchaV2TaskProxyless"
            ]
          },
          "websiteURL": {
            "type": "string",
            "format": "uri",
            "description": "The page the challenge appears on."
          },
          "websiteKey": {
            "type": "string",
            "description": "The challenge provider's site key."
          },
          "isInvisible": {
            "type": "boolean",
            "description": "reCAPTCHA v2 only. Never set for Turnstile."
          },
          "body": {
            "type": "string",
            "contentEncoding": "base64",
            "description": "Image OCR only: the image itself."
          },
          "proxyType": {
            "type": "string",
            "enum": [
              "http",
              "https",
              "socks4",
              "socks5"
            ]
          },
          "proxyAddress": {
            "type": "string"
          },
          "proxyPort": {
            "type": "integer"
          },
          "proxyLogin": {
            "type": "string"
          },
          "proxyPassword": {
            "type": "string"
          }
        }
      },
      "CreateTaskRequest": {
        "type": "object",
        "required": [
          "clientKey",
          "task"
        ],
        "properties": {
          "clientKey": {
            "type": "string"
          },
          "task": {
            "$ref": "#/components/schemas/Task"
          },
          "callbackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Optional. Delivered once, signed, and never a substitute for polling."
          }
        }
      },
      "CreateTaskResponse": {
        "type": "object",
        "required": [
          "errorId",
          "taskId"
        ],
        "properties": {
          "errorId": {
            "type": "integer",
            "enum": [
              0
            ]
          },
          "taskId": {
            "type": "string",
            "pattern": "^[0-9]+$",
            "description": "Digits, carried as a string. Random rather than sequential, so one account cannot enumerate another's by counting.",
            "examples": [
              "4687215434752799"
            ]
          }
        }
      },
      "GetTaskResultRequest": {
        "type": "object",
        "required": [
          "clientKey",
          "taskId"
        ],
        "properties": {
          "clientKey": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          }
        }
      },
      "GetTaskResultResponse": {
        "type": "object",
        "required": [
          "errorId",
          "status"
        ],
        "properties": {
          "errorId": {
            "type": "integer",
            "enum": [
              0
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "ready"
            ]
          },
          "solution": {
            "type": "object",
            "description": "Present when ready. The key depends on the captcha type: gRecaptchaResponse for reCAPTCHA, token for Turnstile, text for image OCR."
          },
          "cost": {
            "type": [
              "string",
              "null"
            ],
            "description": "Charged for this solve, in dollars. Zero in sandbox."
          },
          "solveTime": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Milliseconds from dispatch to verdict."
          }
        }
      },
      "GetBalanceResponse": {
        "type": "object",
        "required": [
          "errorId",
          "balance"
        ],
        "properties": {
          "errorId": {
            "type": "integer",
            "enum": [
              0
            ]
          },
          "balance": {
            "type": "number"
          },
          "currency": {
            "type": "string",
            "examples": [
              "USD"
            ]
          }
        }
      },
      "ReportRequest": {
        "type": "object",
        "required": [
          "clientKey",
          "taskId"
        ],
        "properties": {
          "clientKey": {
            "type": "string"
          },
          "taskId": {
            "type": "string"
          }
        }
      },
      "ReportResponse": {
        "type": "object",
        "required": [
          "errorId",
          "status"
        ],
        "properties": {
          "errorId": {
            "type": "integer",
            "enum": [
              0
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "reported"
            ]
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "errorId",
          "errorCode",
          "errorDescription"
        ],
        "properties": {
          "errorId": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "errorCode": {
            "type": "string",
            "enum": [
              "ERROR_KEY_DOES_NOT_EXIST",
              "ERROR_ZERO_BALANCE",
              "ERROR_NO_SUCH_CAPCHA_ID",
              "ERROR_CAPTCHA_UNSOLVABLE",
              "ERROR_NO_SLOT_AVAILABLE",
              "ERROR_TASK_ABSENT",
              "ERROR_TASK_NOT_SUPPORTED",
              "ERROR_INVALID_TASK_DATA",
              "ERROR_PROXY_REQUIRED",
              "ERROR_RATE_LIMIT",
              "ERROR_TIMEOUT",
              "ERROR_UNKNOWN"
            ]
          },
          "errorDescription": {
            "type": "string"
          }
        }
      }
    }
  }
}
