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.
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.
- Register with an email and password — you get a first API key and a balance of
100requests. - Send the key in the
X-API-Keyheader on every masking call (aBearertoken is also accepted). - 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"
| Situation | HTTP status |
|---|---|
| Missing / invalid / revoked key | 401 Unauthorized |
| Key balance exhausted | 402 Payment Required |
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.
| Field | Choices | Default | Notes |
|---|---|---|---|
| Accept (input filter) | All / PNG / JPEG / WEBP / BMP / TIFF / GIF / PDF | All | Limits which file types the picker & validation allow. |
| Mask mode | First 8 / First 4 / Full | First 8 | How many of the 12 digits to hide. |
| Output format | Auto / PNG / JPEG / PDF | Auto | Auto mirrors the input. Image on a multi-page PDF falls back to PDF. |
| Output delivery | Preview in page / Direct binary download | Preview | Binary applies to a single file; ignored in a batch. |
| Mask style | Solid box / Blur / Pixelate / Solid + label | Solid | How a region is obscured. |
| Label text | ≤ 20 chars | XXXX | Only shown/required when style is Solid + label. |
| If not an Aadhaar | Reject / Pass through | Reject | Pass through returns the original unmasked instead of erroring. |
| Also redact (extra PII) | QR · Photo · VID · DOB · Name · Address | none | Opt-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
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
Send multipart/form-data with the uploaded file plus any of the optional form fields below.
| Field | Type | Allowed values | Default |
|---|---|---|---|
file | file | image or PDF (required) | — |
document_type | string | a registered type id (see GET /api/documents) | aadhaar |
mode | string | first_4 first_8 full | first_8 |
output_format | string | auto png jpeg pdf | auto |
response | string | json binary | json |
mask_style | string | solid blur pixelate label | solid |
label_text | string | text for the label style | XXXX |
extras | string | comma list: qr,photo,vid,dob,name,address | (empty) |
on_not_aadhaar | string | raise passthrough | raise |
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": { }
}
| Field | Meaning |
|---|---|
is_aadhaar | Whether the input was recognised as an Aadhaar card. |
document_type | The document type that was masked, e.g. aadhaar. Also drives the download filename (masked_<type>.<ext>). |
side | Detected card side: front, back, or unknown. |
aadhaar_number_found | Whether a Verhoeff-valid 12-digit number was located. |
masked_number | The number with the configured digits hidden, e.g. XXXX XXXX 9012. |
rotation_applied | Degrees the frame was rotated to find the number (0 if upright). |
output_mime | MIME type of masked_file_base64. |
masked_file_base64 | The masked file, base64-encoded. |
page_count / pages | Frames 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.
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 }
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:
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)
| Option | Type | Default | Description |
|---|---|---|---|
mode | MaskMode | FIRST_8 | How many digits to mask. |
document_type | str | "aadhaar" | Which registered document type to detect and mask. |
output_format | OutputFormat | AUTO | Output file type; AUTO mirrors input. |
jpeg_quality | int | 90 | Quality (1–95) when output is JPEG. |
mask_style | MaskStyle | SOLID | Solid / blur / pixelate / label. |
label_text | str | "XXXX" | Text drawn for the LABEL style. |
pixelate_blocks | int | 10 | Mosaic coarseness for PIXELATE (smaller = coarser). |
mask_color | (r,g,b) | (0,0,0) | RGB of the solid box. |
padding | int | 3 | Extra px painted around each box. |
require_verhoeff | bool | True | Only accept checksum-valid numbers. |
min_confidence | float | 0.30 | OCR confidence floor. |
rotation_fallback | bool | True | Retry rotated copies if number not found. |
rotations | tuple | (90,180,270) | Angles tried by the fallback. |
languages | list | ["en"] | EasyOCR languages. |
use_gpu | bool | False | Run OCR on GPU if available. |
pdf_dpi | int | 200 | PDF render resolution. |
pdf_password | str? | None | Password for protected PDFs / e-Aadhaar. |
max_pages | int? | None | Cap PDF pages processed. |
max_image_dimension | int? | None | Downscale frames whose longest side exceeds this. |
require_strong_keyword | bool | False | Require a UIDAI/Aadhaar keyword to treat input as Aadhaar. |
on_not_aadhaar | OnUnmasked | RAISE | Raise vs. pass through when not an Aadhaar. |
on_number_not_found | OnUnmasked | RAISE | Same, when no number is located. |
mask_qr_code | bool | False | Blank the QR code. |
mask_photo | bool | False | Blank the portrait (face detection). |
mask_vid | bool | False | Mask the 16-digit Virtual ID. |
mask_dob | bool | False | Mask date / year of birth. |
mask_name | bool | False | experimental keyword-anchored name redaction. |
mask_address | bool | False | experimental keyword-anchored address redaction. |
opencv-python-headless). If a detector fails, it is skipped gracefully and the number is still masked.Enum values
| Enum | Values | Result / meaning |
|---|---|---|
MaskMode | first_4 | XXXX 5678 9012 |
first_8 default | XXXX XXXX 9012 (UIDAI standard) | |
full | XXXX XXXX XXXX | |
OutputFormat | auto default / png / jpeg / pdf | Output file type; auto mirrors input. |
MaskStyle | solid default / blur / pixelate / label | How a region is obscured. |
OnUnmasked | raise default / passthrough | Error vs. return the input unchanged. |
Errors & status codes
Error responses share the shape { "error": "...", "error_type": "...", "detail": "..." }.
| Status | error_type | When |
|---|---|---|
400 | — | Bad config value — e.g. mode=first_9. The message lists the allowed values. |
400 | InvalidInputError | Not valid base64 / not a readable image or PDF. |
422 | NotAadhaarError | Image isn’t recognised as an Aadhaar card. |
422 | AadhaarNumberNotFoundError | Looks 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
| Variable | Default | Meaning |
|---|---|---|
AADHAAR_BACKEND | pool | Job backend: pool (in-process) or redis (scale-out). |
AADHAAR_WORKERS | 2 | Process-pool size for the pool backend. |
AADHAAR_OUTPUT_DIR | unset | Write masked files to disk; enables the file-serving endpoint. |
AADHAAR_RETURN_BASE64 | 1 | Include inline base64 in job results (set 0 for very large jobs). |
REDIS_URL | redis://localhost:6379/0 | Connection for the redis backend. |
RQ_QUEUE | aadhaar | Queue name. |
AADHAAR_REQUIRE_API_KEY | 1 | Require an API key on the masking endpoints. Set 0 to open them for local dev. |
AADHAAR_AUTH_DB | app/data/auth.db | SQLite file holding accounts, API keys, and usage. |
AADHAAR_DEFAULT_KEY_BALANCE | 100 | Prepaid request balance granted to each new API key. |
AADHAAR_SESSION_TTL | 604800 | Dashboard login session lifetime, in seconds (7 days). |