Documentation

Usage & API Reference

Every option, the exact request and response shapes, error codes, and copy-paste examples for masking the Aadhaar number on images and PDFs — over the web UI, the HTTP API, and the Python SDK.

Getting started

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt   # first run is large (pulls PyTorch)
.venv/bin/uvicorn app.server:app --host 127.0.0.1 --port 8000

Open http://127.0.0.1:8000 for the UI. The API base URL is the same host.

The first mask call downloads and warms the offline OCR models, so it is slower than later calls. All OCR runs locally — no image or PII leaves the machine.

Accounts & API keys

The masking endpoints (/api/mask and /api/jobs) require an API key. Each key carries a prepaid request balance: every successful call costs one request, and the key stops working once the balance hits zero.

  1. Register with an email and password — you get a first API key and a balance of 100 requests.
  2. Send the key in the X-API-Key header on every masking call (a Bearer token is also accepted).
  3. Manage keys and watch usage on your dashboard — create or revoke keys, and top up a balance.
curl -s -X POST http://127.0.0.1:8000/api/mask \
  -H "X-API-Key: tk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@aadhaar.jpg"
SituationHTTP status
Missing / invalid / revoked key401 Unauthorized
Key balance exhausted402 Payment Required
Keys and passwords are stored hashed (SHA-256 for keys, PBKDF2 for passwords) — a raw key is shown only once, at creation. Run the server behind HTTPS so keys aren’t sent in clear. To disable the requirement for local development, set AADHAAR_REQUIRE_API_KEY=0.

Web UI options

Drag & drop one or more files onto the dropzone (or click to browse), set the options, and click Mask. One file gives an instant before/after preview; several files run as a batch with a results grid.

FieldChoicesDefaultNotes
Accept (input filter)All / PNG / JPEG / WEBP / BMP / TIFF / GIF / PDFAllLimits which file types the picker & validation allow.
Mask modeFirst 8 / First 4 / FullFirst 8How many of the 12 digits to hide.
Output formatAuto / PNG / JPEG / PDFAutoAuto mirrors the input. Image on a multi-page PDF falls back to PDF.
Output deliveryPreview in page / Direct binary downloadPreviewBinary applies to a single file; ignored in a batch.
Mask styleSolid box / Blur / Pixelate / Solid + labelSolidHow a region is obscured.
Label text≤ 20 charsXXXXOnly shown/required when style is Solid + label.
If not an AadhaarReject / Pass throughRejectPass through returns the original unmasked instead of erroring.
Also redact (extra PII)QR · Photo · VID · DOB · Name · AddressnoneOpt-in. Name & Address are experimental.

Required fields are marked with a red *. Validation runs live (on type, blur, and submit); on a problem a toast says “Please fix the highlighted field before masking” and focus jumps to the first invalid field.

List document types

GET/api/documents

Returns the document types the masker can detect, plus the default. Use the id values as the document_type field on /api/mask and /api/jobs. Today only Aadhaar is implemented; new types appear here automatically as they are registered.

curl -s http://127.0.0.1:8000/api/documents
{
  "documents": [
    { "id": "aadhaar", "label": "Aadhaar",
      "file_label": "Aadhaar file(s) — PNG / JPG / PDF" }
  ],
  "default": "aadhaar"
}

Mask a single file

POST/api/mask

Send multipart/form-data with the uploaded file plus any of the optional form fields below.

FieldTypeAllowed valuesDefault
filefileimage or PDF (required)
document_typestringa registered type id (see GET /api/documents)aadhaar
modestringfirst_4 first_8 fullfirst_8
output_formatstringauto png jpeg pdfauto
responsestringjson binaryjson
mask_stylestringsolid blur pixelate labelsolid
label_textstringtext for the label styleXXXX
extrasstringcomma list: qr,photo,vid,dob,name,address(empty)
on_not_aadhaarstringraise passthroughraise

All requests must include your X-API-Key header — see Accounts & API keys.

Request — JSON response (default)

curl -s -X POST http://127.0.0.1:8000/api/mask \
  -H "X-API-Key: tk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@aadhaar.jpg" \
  -F "mode=first_8" \
  -F "mask_style=blur" \
  -F "extras=qr,photo"

Request — raw binary file back

curl -s -X POST http://127.0.0.1:8000/api/mask \
  -H "X-API-Key: tk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -F "file=@aadhaar.pdf" \
  -F "response=binary" \
  -o masked_aadhaar.pdf -D headers.txt

With response=binary the body is the masked file itself; metadata comes back in headers: X-Masked-Number, X-Aadhaar-Side, X-Page-Count, and Content-Disposition.

Mask response (JSON)

{
  "is_aadhaar": true,
  "document_type": "aadhaar",      // which registered type was masked
  "side": "front",                 // "front" | "back" | "unknown"
  "aadhaar_number_found": true,
  "mask_mode": "first_8",
  "masked_number": "XXXX XXXX 9012",
  "rotation_applied": 0,           // degrees auto-rotated before masking
  "output_mime": "image/png",      // image/png | image/jpeg | application/pdf
  "masked_file_base64": "iVBORw0KGgo...",
  "page_count": 1,
  "pages": [                        // per-page breakdown (PDFs)
    { "index": 0, "is_aadhaar": true, "side": "front",
      "aadhaar_number_found": true, "masked_number": "XXXX XXXX 9012",
      "rotation_applied": 0 }
  ],
  "debug": { }
}
FieldMeaning
is_aadhaarWhether the input was recognised as an Aadhaar card.
document_typeThe document type that was masked, e.g. aadhaar. Also drives the download filename (masked_<type>.<ext>).
sideDetected card side: front, back, or unknown.
aadhaar_number_foundWhether a Verhoeff-valid 12-digit number was located.
masked_numberThe number with the configured digits hidden, e.g. XXXX XXXX 9012.
rotation_appliedDegrees the frame was rotated to find the number (0 if upright).
output_mimeMIME type of masked_file_base64.
masked_file_base64The masked file, base64-encoded.
page_count / pagesFrames processed; per-page results for multi-page PDFs.

Bulk async jobs

For large lists, submit a job and poll — the submit returns immediately and work runs on a warm worker pool.

POST/api/jobs

JSON body. images is a list of base64 strings (bare or data: URI); the rest mirror the mask options.

curl -s -X POST http://127.0.0.1:8000/api/jobs \
  -H "Content-Type: application/json" \
  -d '{
    "images": ["<b64>", "<b64>"],
    "document_type": "aadhaar",
    "mode": "first_8",
    "mask_style": "solid",
    "extras": ["qr", "photo"],
    "on_not_aadhaar": "passthrough"
  }'
# -> { "job_id": "a1b2c3", "status": "running", "total": 2, "done": 0, "errors": 0 }
GET/api/jobs/{job_id}

Poll status. Add ?include_results=false for a lightweight progress check.

curl -s "http://127.0.0.1:8000/api/jobs/a1b2c3?include_results=false"
# -> { "status": "running" | "completed", "done": 2, "total": 2, "errors": 0 }

curl -s "http://127.0.0.1:8000/api/jobs/a1b2c3"   # full per-item results

Each result item carries its own status (ok / error), so one bad image never aborts the batch. When disk output is enabled (AADHAAR_OUTPUT_DIR), masked files are served at:

GET/api/jobs/{job_id}/files/{index}

Python SDK

from aadhaar_masker import mask_aadhaar, MaskConfig, MaskMode, MaskStyle

result = mask_aadhaar(
    image_bytes_or_base64,
    MaskConfig(mode=MaskMode.FIRST_8, mask_style=MaskStyle.BLUR, mask_qr_code=True),
)

if result.is_aadhaar and result.aadhaar_number_found:
    print(result.side)                 # "front" | "back" | "unknown"
    print(result.masked_number)        # "XXXX XXXX 9012"
    masked_b64   = result.masked_file_base64
    masked_bytes = result.masked_file_bytes
    data_uri     = result.masked_file_data_uri

mask_aadhaar accepts a base64 str (bare or data: URI) or raw bytes/bytearray/memoryview. For bulk, use mask_aadhaar_bulk(list, config, workers=4) (multi-process) or mask_aadhaar_batch(...) (sequential). See the project README.md for the full bulk/Redis guide.

All config options (MaskConfig)

OptionTypeDefaultDescription
modeMaskModeFIRST_8How many digits to mask.
document_typestr"aadhaar"Which registered document type to detect and mask.
output_formatOutputFormatAUTOOutput file type; AUTO mirrors input.
jpeg_qualityint90Quality (1–95) when output is JPEG.
mask_styleMaskStyleSOLIDSolid / blur / pixelate / label.
label_textstr"XXXX"Text drawn for the LABEL style.
pixelate_blocksint10Mosaic coarseness for PIXELATE (smaller = coarser).
mask_color(r,g,b)(0,0,0)RGB of the solid box.
paddingint3Extra px painted around each box.
require_verhoeffboolTrueOnly accept checksum-valid numbers.
min_confidencefloat0.30OCR confidence floor.
rotation_fallbackboolTrueRetry rotated copies if number not found.
rotationstuple(90,180,270)Angles tried by the fallback.
languageslist["en"]EasyOCR languages.
use_gpuboolFalseRun OCR on GPU if available.
pdf_dpiint200PDF render resolution.
pdf_passwordstr?NonePassword for protected PDFs / e-Aadhaar.
max_pagesint?NoneCap PDF pages processed.
max_image_dimensionint?NoneDownscale frames whose longest side exceeds this.
require_strong_keywordboolFalseRequire a UIDAI/Aadhaar keyword to treat input as Aadhaar.
on_not_aadhaarOnUnmaskedRAISERaise vs. pass through when not an Aadhaar.
on_number_not_foundOnUnmaskedRAISESame, when no number is located.
mask_qr_codeboolFalseBlank the QR code.
mask_photoboolFalseBlank the portrait (face detection).
mask_vidboolFalseMask the 16-digit Virtual ID.
mask_dobboolFalseMask date / year of birth.
mask_nameboolFalseexperimental keyword-anchored name redaction.
mask_addressboolFalseexperimental keyword-anchored address redaction.
QR, photo, blur, and pixelate styles require OpenCV (opencv-python-headless). If a detector fails, it is skipped gracefully and the number is still masked.

Enum values

EnumValuesResult / meaning
MaskModefirst_4XXXX 5678 9012
first_8 defaultXXXX XXXX 9012 (UIDAI standard)
fullXXXX XXXX XXXX
OutputFormatauto default / png / jpeg / pdfOutput file type; auto mirrors input.
MaskStylesolid default / blur / pixelate / labelHow a region is obscured.
OnUnmaskedraise default / passthroughError vs. return the input unchanged.

Errors & status codes

Error responses share the shape { "error": "...", "error_type": "...", "detail": "..." }.

Statuserror_typeWhen
400Bad config value — e.g. mode=first_9. The message lists the allowed values.
400InvalidInputErrorNot valid base64 / not a readable image or PDF.
422NotAadhaarErrorImage isn’t recognised as an Aadhaar card.
422AadhaarNumberNotFoundErrorLooks like an Aadhaar but no number could be located.
500(varies)Unexpected server error — a friendly message is returned, details in the server log.

Set on_not_aadhaar=passthrough to receive the original file (HTTP 200) instead of a 422 when the input isn’t a maskable Aadhaar.

Environment variables

VariableDefaultMeaning
AADHAAR_BACKENDpoolJob backend: pool (in-process) or redis (scale-out).
AADHAAR_WORKERS2Process-pool size for the pool backend.
AADHAAR_OUTPUT_DIRunsetWrite masked files to disk; enables the file-serving endpoint.
AADHAAR_RETURN_BASE641Include inline base64 in job results (set 0 for very large jobs).
REDIS_URLredis://localhost:6379/0Connection for the redis backend.
RQ_QUEUEaadhaarQueue name.
AADHAAR_REQUIRE_API_KEY1Require an API key on the masking endpoints. Set 0 to open them for local dev.
AADHAAR_AUTH_DBapp/data/auth.dbSQLite file holding accounts, API keys, and usage.
AADHAAR_DEFAULT_KEY_BALANCE100Prepaid request balance granted to each new API key.
AADHAAR_SESSION_TTL604800Dashboard login session lifetime, in seconds (7 days).