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.
| Approach | Cost per solve | At 1M polls/day | Verdict |
|---|---|---|---|
| Human solving service | ~₹0.09 | ~₹3.3 crore/yr | Impossible |
| Commercial OCR API | ~₹0.05–0.15 | ~₹2–5 crore/yr | Impossible |
| Self-hosted CNN on CPU | ~₹0.0001 | ~₹35,000/yr | The 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 PaddleOCRPP-OCRv5_mobile_rec, with a fine-tuned model incaptcha_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.
| Type | Seen at | Strategy | Difficulty |
|---|---|---|---|
| Plain numeric | Many district eCourts | Fine-tuned CRNN | Low |
| Alphanumeric, light noise | Several High Courts | Fine-tuned CRNN | Low |
| Distorted + strikethrough | Some tribunals | Denoise preprocessing, then CRNN | Medium |
| Arithmetic ("4 + 7 = ?") | Assorted | OCR, then evaluate expression | Low |
| Audio alternative | Accessibility fallbacks | Speech-to-text — often far easier than the image | Low |
| Session/token only | Some modern portals | No image — replay session correctly | None |
| reCAPTCHA / hCaptcha | A minority, growing | Not solvable in-house | Blocked |
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.