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
| Status | Meaning | Retry? |
|---|---|---|
| 200 | Success — captcha solved | — |
| 400 | Bad request — missing or invalid parameter | No — fix the request |
| 401 | Unauthorized — missing or invalid API key | No — check your key |
| 402 | Payment required — insufficient credits | No — top up credits |
| 413 | Payload too large — image exceeds 2 MB | No — compress the image |
| 415 | Unsupported media type — not PNG/JPEG/GIF/WebP | No — convert the image |
| 429 | Too many requests — rate limit exceeded | Yes — wait & retry |
| 500 | Server error — transient model or infra issue | Yes — retry with backoff |
| 503 | Service unavailable — maintenance or overload | Yes — retry with backoff |
Error code reference
| code | error message | Fix |
|---|---|---|
API_KEY_MISSING | API key required | Add X-API-Key header |
API_KEY_INVALID | Invalid API key | Check your key in the dashboard |
INSUFFICIENT_CREDITS | Not enough credits | Purchase more credits |
IMAGE_MISSING | No image provided | Include image field in request |
IMAGE_TOO_LARGE | Image exceeds 2 MB limit | Resize or compress the image |
IMAGE_FORMAT_INVALID | Unsupported image format | Use PNG, JPEG, GIF, or WebP |
RATE_LIMIT_EXCEEDED | Too many requests | See Rate Limits guide |
MODEL_ERROR | Prediction failed | Retry — transient error |
Retry strategy
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 === truebefore readingtext - 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-Afterheader when present - Log
credits_remainingto catch low-balance situations early - Keep your API key in an environment variable — never hardcode it