Error Handling

Every API call can fail. This guide shows you every error the FastCaptcha API can return, what each one means, and exactly how to handle it in production code.

Error response format

Every error from the FastCaptcha API returns JSON with a consistent shape:

{
  "success": false,
  "error": "Human-readable error message",
  "code": "ERROR_CODE"
}

On success, success is true and error is absent:

{
  "success": true,
  "text": "XK92B",
  "confidence": 0.97,
  "processing_time": 0.31,
  "credits_remaining": 84
}

HTTP status codes

StatusMeaningRetry?
200Success — captcha solved
400Bad request — missing or invalid parameterNo — fix the request
401Unauthorized — missing or invalid API keyNo — check your key
402Payment required — insufficient creditsNo — top up credits
413Payload too large — image exceeds 2 MBNo — compress the image
415Unsupported media type — not PNG/JPEG/GIF/WebPNo — convert the image
429Too many requests — rate limit exceededYes — wait & retry
500Server error — transient model or infra issueYes — retry with backoff
503Service unavailable — maintenance or overloadYes — retry with backoff

Error code reference

codeerror messageFix
API_KEY_MISSINGAPI key requiredAdd X-API-Key header
API_KEY_INVALIDInvalid API keyCheck your key in the dashboard
INSUFFICIENT_CREDITSNot enough creditsPurchase more credits
IMAGE_MISSINGNo image providedInclude image field in request
IMAGE_TOO_LARGEImage exceeds 2 MB limitResize or compress the image
IMAGE_FORMAT_INVALIDUnsupported image formatUse PNG, JPEG, GIF, or WebP
RATE_LIMIT_EXCEEDEDToo many requestsSee Rate Limits guide
MODEL_ERRORPrediction failedRetry — transient error

Retry strategy

Only retry on 429 and 5xx. Never retry on 4xx (except 429) — the request will keep failing until you fix the underlying problem.

Use exponential backoff with jitter to avoid thundering-herd problems:

delay = min(base_delay * 2^attempt + random_jitter, max_delay)

# Example: base=1s, max=32s, jitter up to 1s
# attempt 0 → ~1s
# attempt 1 → ~2s
# attempt 2 → ~4s
# attempt 3 → ~8s
# attempt 4 → ~16s  (cap at max_delay after this)

We recommend max 3–5 retries for model errors, and honouring the Retry-After header on 429 responses.

Code examples

Python

import time, random, requests

API_KEY = "YOUR_API_KEY"
API_URL = "https://fastcaptcha.org/api/v1/ocr/"
MAX_RETRIES = 4

def solve_captcha(image_path: str) -> str:
    for attempt in range(MAX_RETRIES):
        try:
            with open(image_path, "rb") as f:
                resp = requests.post(
                    API_URL,
                    headers={"X-API-Key": API_KEY},
                    files={"image": f},
                    timeout=15,
                )

            data = resp.json()

            if resp.status_code == 200 and data.get("success"):
                return data["text"]

            # Retriable errors
            if resp.status_code in (429, 500, 503):
                retry_after = int(resp.headers.get("Retry-After", 0))
                delay = retry_after or min(2 ** attempt + random.random(), 32)
                print(f"Retryable error {resp.status_code}, waiting {delay:.1f}s (attempt {attempt+1})")
                time.sleep(delay)
                continue

            # Non-retriable — raise immediately
            raise ValueError(f"API error {resp.status_code}: {data.get('error')}")

        except requests.Timeout:
            if attempt < MAX_RETRIES - 1:
                time.sleep(2 ** attempt)
            else:
                raise

    raise RuntimeError("Max retries exceeded")

Node.js

const axios = require("axios");
const FormData = require("form-data");
const fs = require("fs");

const API_KEY = "YOUR_API_KEY";
const MAX_RETRIES = 4;

async function solveCaptcha(imagePath) {
  for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
    const form = new FormData();
    form.append("image", fs.createReadStream(imagePath));

    try {
      const { data } = await axios.post(
        "https://fastcaptcha.org/api/v1/ocr/",
        form,
        { headers: { "X-API-Key": API_KEY, ...form.getHeaders() }, timeout: 15000 }
      );
      return data.text;
    } catch (err) {
      const status = err.response?.status;
      if ([429, 500, 503].includes(status)) {
        const retryAfter = parseInt(err.response.headers["retry-after"] || "0");
        const delay = retryAfter || Math.min(Math.pow(2, attempt) + Math.random(), 32);
        console.log(`Retryable ${status}, waiting ${delay.toFixed(1)}s`);
        await new Promise(r => setTimeout(r, delay * 1000));
      } else {
        throw err; // non-retriable
      }
    }
  }
  throw new Error("Max retries exceeded");
}

Production checklist

  • Always check success === true before reading text
  • Set a request timeout (10–15 seconds recommended)
  • Only retry on 429 and 5xx — never on 4xx (except 429)
  • Use exponential backoff with jitter
  • Honour the Retry-After header when present
  • Log credits_remaining to catch low-balance situations early
  • Keep your API key in an environment variable — never hardcode it