Section 07

Captcha Pipeline

Most court sites gate case search behind a captcha. This section covers the solver, the per-court model registry, and the learning loop that turns every failure into training data.

Why this must be built, not bought

The instinct is to use a solving service — 2Captcha, Anti-Captcha, CapSolver. Run the numbers against the per-poll budget and that option disappears.

ApproachCost per solveAt 1M polls/dayVerdict
Human solving service~₹0.09~₹3.3 crore/yrImpossible
Commercial OCR API~₹0.05–0.15~₹2–5 crore/yrImpossible
Self-hosted CNN on CPU~₹0.0001~₹35,000/yrThe only viable option

The decisive number

A bought captcha solve costs roughly ₹0.09. The entire budget for a complete poll — fetch, parse, store, notify — is ₹0.22. Outsourcing captcha would consume about 40% of the unit economics for one step of a multi-step job.

A self-hosted model is roughly 1,000× cheaper. This is not a preference or an engineering indulgence; it is the difference between a viable business and one that loses money on every customer.

A solving service still has one legitimate role: as an emergency fallback for a court whose captcha suddenly changes, buying time while a model is retrained. Budget it as an incident cost with a hard monthly cap, not as an architecture.

What already exists

The old system got further here than anywhere else, and this work should carry over.

  • captcha_app — FastAPI service wrapping PaddleOCR PP-OCRv5_mobile_rec, with a fine-tuned model in captcha_rec_infer/. Accepts base64, URL, and batch input.
  • ocr-work — training artifacts for a model fine-tuned on Allahabad High Court captcha data.

The architecture is right. What it lacks is the thing that makes it durable: a per-court model registry and a feedback loop. Today one model was trained by hand for one court, and there is no mechanism to notice it degrading or to extend the approach to the other ~69.

District eCourts are one captcha, not hundreds

Reconnaissance of the old repos shows every district court portal running the same ecourtindia_v6 codebase with Securimage, the open-source PHP captcha library — 175 and 112 references respectively. Allahabad High Court uses it too.

So the district judiciary is not hundreds of captcha problems. It is one Securimage model, and because Securimage's distortion parameters are configurable but bounded, a model trained across a sample of districts should generalise nationally. This is the best coverage-per-effort ratio anywhere in the fleet — see Court Coverage.

And Securimage ships an audio captcha. The captured markup already contains securimage_play, captcha_image_audio and SecurimageAudio. Spoken digits on a controlled noise floor is a far easier problem than the deliberately distorted image. Test the audio path before training anything — it may remove the need for an eCourts image model entirely.

Captcha taxonomy

Court sites are not uniform. The solver dispatches by type, declared in the court profile.

TypeSeen atStrategyDifficulty
Plain numericMany district eCourtsFine-tuned CRNNLow
Alphanumeric, light noiseSeveral High CourtsFine-tuned CRNNLow
Distorted + strikethroughSome tribunalsDenoise preprocessing, then CRNNMedium
Arithmetic ("4 + 7 = ?")AssortedOCR, then evaluate expressionLow
Audio alternativeAccessibility fallbacksSpeech-to-text — often far easier than the imageLow
Session/token onlySome modern portalsNo image — replay session correctlyNone
reCAPTCHA / hCaptchaA minority, growingNot solvable in-houseBlocked

Two honest notes

Audio is often the cheap door. Where a site offers an accessibility audio alternative, speech-to-text is frequently more reliable than image OCR and needs no per-court training. Check for it before training a model.

reCAPTCHA and hCaptcha are out of scope. Solving them means either a paid human farm — economically impossible per the table above — or evasion techniques this project should not build. If a court moves to reCAPTCHA, the honest answers are: an official data feed, a registry agreement, or dropping automated coverage and telling users plainly. Design the product so a court can be marked manual-only without breaking.

Good news on this front: a scan of the priority courts found no reCAPTCHA or hCaptcha on any of the search paths actually used. Punjab & Haryana runs commercial bot protection, but only on its frontend domain — its public JSON API is unguarded and needs no captcha at all.

Solve pipeline

flowchart TB
    A[Connector needs a captcha] --> B[Fetch image + session]
    B --> C{Court profile:
captcha type} C -->|session only| Z[No solve needed] C -->|audio available| AU[Speech-to-text] C -->|image| D[Preprocess:
denoise, threshold, deskew] D --> E[Per-court model
from registry] E --> F[Predict + confidence] F --> G{Confidence
above threshold?} G -->|no| H[Retry with alternate
preprocessing, max N] H --> E G -->|yes| I[Submit to court] AU --> I I --> J{Court accepted?} J -->|yes| K[Record success
+ verified label] J -->|no| L[Record FAILED_CAPTCHA
store image + prediction] L --> M{Attempts
exhausted?} M -->|no| B M -->|yes| N[Fail the poll
increment court failure count]

Fig 7.1 — Captcha solve path

The free labelled data nobody collects

When the court accepts a solve, that is a verified ground-truth label — the court itself confirmed the string was correct. Every successful poll silently produces one perfectly labelled training example, at zero cost.

The old system throws all of it away. Capturing it means each court accumulates a growing, self-labelling training set, and retraining becomes routine rather than a research project.

The learning loop

This is the mechanism that keeps ~70 courts solvable without a standing research team.

flowchart LR
    A[FAILED_CAPTCHA
records accumulate] --> B[Nightly job:
cluster by court] B --> C[Perceptual hash
dedupe] C --> D{Novel pattern?} D -->|no| E[Add to retrain pool] D -->|yes| F[Send sample to
vision LLM] F --> G[LLM: read the text,
describe the distortion] G --> H{LLM confident?} H -->|yes| I[Provisional label
+ pattern note] H -->|no| J[Human review queue] I --> K[Verify: replay against
the court] K -->|accepted| L[Confirmed label] K -->|rejected| J J --> L L --> E E --> M{Pool large enough
or accuracy dropping?} M -->|yes| N[Retrain per-court model] N --> O[Shadow evaluate
vs current model] O -->|better| P[Promote in registry] O -->|worse| J

Fig 7.2 — Failed-captcha learning loop

Where the AI genuinely earns its place

A vision LLM is far too expensive to sit in the solve path — that is the ₹0.09 problem again. It is well suited to the research path, where volume is tiny and value per call is high:

  • Labelling novel patterns. Dozens of images a night, not millions. At that volume the cost is negligible.
  • Describing what changed. "This court has added a diagonal strikethrough and shifted to a serif face" is a useful engineering signal, not just a label.
  • Flagging type changes. Recognising that a court has switched to reCAPTCHA — which needs a human decision, not a retrain.

Note the verification step in Fig 7.2: an LLM label is provisional until replayed against the court and accepted. Training on unverified model output is how a solver quietly poisons itself.

Deduplication matters more than it sounds

A broken court produces thousands of near-identical failures overnight. Perceptual hashing collapses those to a handful of distinct patterns before anything reaches the LLM or a human. Without it, the research queue is unusable within a day and the AI bill is absurd.

Model registry

Models are versioned artifacts, not files on a server.

captcha_model:
  court_id: allahabad-hc
  version: 7
  base: PP-OCRv5_mobile_rec
  artifact: r2://models/allahabad-hc/v7/
  trained_on: 18_432 samples      # 17_901 verified + 531 reviewed
  charset: "0-9A-Z"
  fixed_length: 6
  accuracy: 0.981                 # held-out test set
  live_accuracy_7d: 0.974         # measured against court acceptance
  promoted_at: 2026-07-14T02:11Z
  previous: 6                     # instant rollback target

Two accuracy figures, deliberately. Held-out test accuracy is what the model claims; live acceptance rate is what the court says. When they diverge, the test set has gone stale — which is itself the earliest signal that a court has changed its captcha.

Shared base, per-court heads

Training 70 independent models from scratch would be wasteful. The registry supports a shared base model fine-tuned per court, so a new court starts from a strong general recogniser and needs hundreds of samples rather than tens of thousands. Courts with similar captcha styles can also share a model outright — the registry maps court_id → model, and nothing requires that mapping to be one-to-one.

Operational safeguards

Accuracy alerting

Live acceptance rate per court, alerting on deviation from baseline. Usually the first sign a court changed something.

Attempt caps

Hard limit on solve attempts per poll. Prevents a broken model hammering a court site.

Instant rollback

Every promotion keeps its predecessor. A bad model is one registry write away from reverted.

Shadow evaluation

New models run alongside the incumbent on live traffic before promotion. No model ships on test-set numbers alone.

Retention and privacy

Captcha images are operational data, not user data, and must never be linked to the user whose poll triggered them. Store the image, the court, the prediction, and the outcome — nothing else. Set a retention window (90 days is ample) and delete on schedule. A growing archive of images tied to user activity is a liability with no upside.

The pattern to notice

Deterministic fast path → failure → AI-assisted research → verified artifact → back into the fast path.

The same shape appears in Document Parsing, where the artifact is a layout template rather than a model. Building both loops on shared infrastructure — failure capture, deduplication, a research queue, human review, versioned promotion — means building it once and applying it twice.