e-Aadhaar downloads are PDFs. So are most bank-submitted KYC packets, and most of what a branch scanner produces. Every page is scanned, the redaction is drawn into the page rather than layered over it, and the count that comes back tells you how many regions across the whole document were covered.
The same endpoint takes PDFs and images. Nothing in the request changes except the file.
# A multi-page e-Aadhaar, masked and written back to disk curl -X POST https://api.maskaadhaar.com/api/v1/mask-aadhaar \ -H "X-API-Key: $MASKAADHAAR_KEY" \ -F "file=@e-aadhaar.pdf" \ -D headers.txt \ --output e-aadhaar_masked.pdf # X-Masked-Count is the total across every page, not per page. grep -i x-masked-count headers.txt
One number to plan around: processing time scales with page count, because every page is rendered and scanned. A single-page image comes back in a couple of seconds; a twelve-page scanned packet takes appreciably longer. Set the client timeout from the longest document you accept, not the average, or a legitimate request gets cut off and retried — which costs time and achieves nothing, since the retry is equally slow.
"PDF" covers three quite different things, and they do not behave the same way. Knowing which sort you are feeding in explains most of the variation people see in results.
| Kind | What it is | How it reads |
|---|---|---|
| Digital e-Aadhaar | Downloaded from the UIDAI portal. Crisp vector text, consistent layout. | The best case. Clean glyphs and a predictable 4-4-4 layout. |
| Scanned document | A physical card or printout put through a scanner. | Behaves like an image. Depends entirely on scan quality, resolution and skew. |
| Photographed and wrapped | A phone photo dropped into a PDF, often by a mobile upload flow. | Hardest. Inherits perspective distortion, shadow and glare from the photograph. |
A backlog is usually a mix of all three, weighted towards the worst two, because the digital e-Aadhaar is a relatively recent habit. That is why a sample of a hundred documents drawn across the age range of an archive predicts a bulk run far better than a sample of the most recent hundred.
Resolution is the single most useful lever you control. A 150 dpi scan of a card renders the Aadhaar digits at a size where OCR is guessing; 300 dpi is where they become reliably legible. If you are commissioning a re-scan of an archive, that setting is worth more than anything you can do afterwards in software.
This is the part that turns a PDF-masking question into a security question, and it is worth stating plainly, because the mistake is extremely common and produces a file that looks perfectly redacted in every viewer.
A PDF is a layered document. Drawing a black rectangle over a number — with an annotation, a highlight, a shape in a PDF editor, or a "redact" tool that only adds an overlay — leaves the original text sitting underneath it. Anyone can recover it:
pdftotext, or any PDF text extractor. The digits are in the output.This has caused real breaches. Redaction-by-annotation has exposed sealed court filings, unredacted names in released government documents, and salary data in published reports — every one of which looked correctly blacked out to the person who published it. A document that passes a visual check can still be carrying the number in its text layer.
The masking here happens at the pixel level. Each page is rasterised, the mask is drawn into the image data, and the output PDF is built from those pages. There is no text layer left underneath, because there is no text layer at all — the covered digits are gone from the file rather than hidden within it.
That has a trade-off worth knowing about: the output is not text-searchable, because it is composed of images. For a masked KYC document that is usually correct, and often desirable — a redacted identity document is not something you generally want indexed by a search system. But if your workflow depends on extracting text from the masked copy, extract it from the original before masking, not from the output.
You can verify this on your own output in one command:
# Should return nothing. If digits appear, whatever produced that file drew a # rectangle over the number instead of removing it. pdftotext e-aadhaar_masked.pdf - | grep -oE '[0-9]{4}[ -]?[0-9]{4}[ -]?[0-9]{4}'
That check is worth running against any masking tool before you trust it with an archive, including this one.
An e-Aadhaar downloaded from the UIDAI portal is encrypted. The password is the first four
letters of the holder's name in capitals followed by their year of birth — so a document
belonging to someone named Sharma born in 1990 opens with SHAR1990.
The API does not accept a password, deliberately. Sending it would mean transmitting a credential derived from the holder's name and date of birth alongside the document it protects, which is a poor trade for saving one local step. Decrypt in your own environment and send the resulting file:
# Remove the password locally, then mask the decrypted copy. import pikepdf with pikepdf.open("e-aadhaar.pdf", password="SHAR1990") as pdf: pdf.save("decrypted.pdf") # decrypted.pdf now goes to the API — and should be deleted afterwards, # since it is an unprotected Aadhaar document sitting on disk.
Delete that intermediate file once the masked copy is stored. It is the one moment in the pipeline where a fully readable, unprotected Aadhaar document exists on your filesystem, and it is easy to leave behind in a temp directory.
An encrypted PDF sent without decrypting returns 422 unsupported_file_type,
because the bytes cannot be parsed as a document at all.
The optional output_format field takes same (the default),
pdf or jpg.
| Value | Input PDF gives you | When to use it |
|---|---|---|
same | A masked PDF, same page count | Default. Right when the masked copy replaces the original in a document store. |
pdf | A masked PDF | Explicit form of the above; useful when the input type varies and the output must not. |
jpg | A masked image of the first page | Thumbnails, previews, or a UI that shows a document without a PDF viewer. |
jpg on a multi-page PDF returns the first page
only. That is the intended behaviour for a preview, and a quiet way to lose pages if
you use it for storage. When the masked copy is the record you keep, use same or
pdf.
X-Masked-Count is the total across every page. That is the right number for
deciding whether the document was masked at all, and the wrong number for assuming every page
was clean.
Consider a four-page KYC packet where pages one and three carry Aadhaar copies. A count of two means two regions were covered somewhere in the document. It does not tell you they were on pages one and three, and it does not prove page three was examined successfully — two regions could both have been on page one.
In practice this matters for one specific case: packets where the same document appears more than once, which is common when a customer submits both sides of a card and a self-attested photocopy. A count noticeably lower than the number of Aadhaar copies you know are in the packet is a signal to review it, even though it is not zero.
For single-document workflows — one card, one file, which is the overwhelming majority of KYC traffic — the rule is simply the one that applies everywhere: zero means not masked, above zero means masked. For assembled packets, compare the count against what you expect to be in the packet, and route the mismatches.
# Multi-document packet: expect one masked region per Aadhaar copy enclosed. regions = int(res.headers["X-Masked-Count"]) if regions == 0: review(doc, "nothing masked") elif regions < expected_copies: # Something was masked, but fewer regions than the packet should contain. review(doc, f"masked {regions}, expected {expected_copies}") else: store(doc)
Multi-page documents take longer per request, so throughput planning differs from an image workload. Tell us your page-count distribution and volume and we will size a key for it.
Request API access Bulk masking guidePost the PDF to https://api.maskaadhaar.com/api/v1/mask-aadhaar exactly as you would an image. Every page is rendered and scanned, and the masked PDF is returned in the response body with the same page count. X-Masked-Count reports the total regions redacted across the whole document, not per page.
Yes. Each page is rasterised and scanned independently, so an Aadhaar number on page four is found just as one on page one. X-Masked-Count is the total across all pages, so for an assembled KYC packet compare it against the number of Aadhaar copies you expect the packet to contain rather than assuming any non-zero count means every page was clean.
No. The redaction is drawn into the page image rather than layered over it, so there is no text underneath to select, copy or extract. This is the difference between real redaction and drawing a black rectangle in a PDF editor, which leaves the original text in the file and has caused real breaches. You can verify it by running pdftotext over the masked output and searching for digits: nothing should come back.
No. The output pages are images, which is the consequence of removing the text layer rather than covering it. For a redacted identity document that is usually the right outcome. If your workflow needs text from the document, extract it from the original before masking rather than from the masked copy.
No, and deliberately so. The e-Aadhaar password is derived from the holder's name and year of birth, so sending it alongside the document would transmit a credential built from the very details the document is being masked to protect. Decrypt locally with a library such as pikepdf and send the decrypted file, then delete that intermediate copy once the masked version is stored. An encrypted PDF sent as-is returns 422 unsupported_file_type.
Scan quality. A 150 dpi scan renders the Aadhaar digits at a size where OCR is guessing; 300 dpi makes them reliably legible. Phone photographs wrapped into a PDF are harder still, because they carry perspective distortion, shadow and glare. Those documents return 200 with X-Masked-Count of zero, cost no quota, and should be routed to review.