# Document reading API

> PDFs and scans to Markdown and tables; invoices, receipts, cheques and bank statements read and checked; Aadhaar masking and redaction.

These read documents without a language model, so they are fast and never make text up. A PDF's own text is read with the position of every word; a scan or photo is read with OCR (Tesseract). Headings come from the size of the text, tables from words that line up in columns, and an invoice's fields from their labels. Files are read and deleted; nothing is kept or logged. Needs a free API key once keys are required (see /keys).

Base URL `https://api.dharun.dev`. No API key needed for most endpoints; a free key (https://api.dharun.dev/keys) raises the limits, and the model endpoints marked below will need one. Answers are JSON, `{ "data": ..., "meta": { ... } }`, and errors are `{ "error": { "code", "message" } }` with a matching HTTP status. Rate limit: 600 units a minute per address without a key, 3000 per key; the `RateLimit-*` headers say what is left.

## PDF or scan to Markdown

`POST /v1/doc/markdown`

Markdown with headings (from text size), lists, paragraphs and tables, page by page with a rule between pages. Text set in columns side by side is read across. Good input for a language model or a search index; method says whether the PDF's text or OCR was used. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/invoice.pdf

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `first` (query): PDF: first page. Default 1.
- `last` (query): PDF: last page. A PDF's own text is read up to 500 pages, a scan up to 10.
- `ocr` (query): PDF: auto (default) reads the PDF's own text and uses OCR only when there is none; true always uses OCR; false never. One of: auto, true, false.
- `lang` (query): OCR languages, as in /v1/ocr: up to three of eng, hin, tam, tel, kan, mal, ben, guj, pan, ori, mar joined by +. Default eng.

Example: `POST` https://api.dharun.dev/v1/doc/markdown?utm_medium=docs-md

Response, with lists and long text shortened:

```json
{
  "data": {
    "markdown": "# Sri Murugan Stores · TAX INVOICE\n\n12 Car Street, Namakkal 637001\n\n| GSTIN 33AABCS1429B1Z1 | Invoice no. | INV-2026-0042 |\n|---|---|---|\n| State Tamil Nadu (33…",
    "pages": 1,
    "method": "text"
  },
  "meta": {
    "note": "Headings come from the size of the text, tables from words that line up in columns. Read from the PDF's own text. A scan without text is read with OCR instead; …"
  }
}
```

## Tables in a PDF or scan

`POST /v1/doc/tables`

Every table found, as rows of cells with its page. A table is three or more rows whose words line up in two or more columns, with or without ruled lines. format=csv returns one table (table=N) as CSV. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/invoice.pdf

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `format` (query): json (default); markdown adds each table as Markdown; csv returns one table as a CSV file. One of: json, markdown, csv.
- `table` (query): With format=csv: which table, from 1. Default 1.
- `first` (query): PDF: first page. Default 1.
- `last` (query): PDF: last page. A PDF's own text is read up to 500 pages, a scan up to 10.
- `ocr` (query): PDF: auto (default) reads the PDF's own text and uses OCR only when there is none; true always uses OCR; false never. One of: auto, true, false.
- `lang` (query): OCR languages, as in /v1/ocr: up to three of eng, hin, tam, tel, kan, mal, ben, guj, pan, ori, mar joined by +. Default eng.

Example: `POST` https://api.dharun.dev/v1/doc/tables?utm_medium=docs-md

Response, with lists and long text shortened:

```json
{
  "data": [
    {
      "page": 1,
      "columns": 3,
      "rows": [
        [
          "GSTIN 33AABCS1429B1Z1",
          "Invoice no.",
          "INV-2026-0042"
        ],
        [
          "State Tamil Nadu (33)",
          "Date",
          "14 Oct 2026"
        ],
        [
          "",
          "Place of supply",
          "Karnataka (29)"
        ]
      ]
    },
    {
      "page": 1,
      "columns": 10,
      "rows": [
        [
          "#",
          "ITEM",
          "HSN/SAC"
        ],
        [
          "1",
          "Basmati rice, 25 kg bag",
          "1006"
        ],
        [
          "2",
          "Steel tiffin box",
          "7323"
        ]
      ]
    },
    {
      "page": 1,
      "columns": 3,
      "rows": [
        [
          "AMOUNT IN WORDS",
          "Taxable value",
          "11,975.00"
        ],
        [
          "Thirteen thousand thirteen rupees only",
          "IGST",
          "1,037.50"
        ],
        [
          "",
          "Round off",
          "0.50"
        ]
      ]
    }
  ],
  "meta": {
    "total": 4,
    "note": "The first row of each table is usually its header. format=csv&table=N gives one table as CSV. Read from the PDF's own text. A scan without text is read with OCR…"
  }
}
```

## Read and check a GST invoice

`POST /v1/doc/invoice`

Reads the invoice number and date, seller and buyer with their GSTINs and states, place of supply, HSN codes, taxable value, CGST, SGST or UTGST, IGST, cess, round off, total, amount in words, IFSC and UPI handle. Then checks them: each GSTIN's check character, the taxes adding up to the total, CGST equal to SGST, the kind of tax against the place of supply, the amount in words against the total, and the invoice number's format (Rule 46). A field not found is null. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/invoice.pdf

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `first` (query): PDF: first page. Default 1.
- `last` (query): PDF: last page. A PDF's own text is read up to 500 pages, a scan up to 10.
- `ocr` (query): PDF: auto (default) reads the PDF's own text and uses OCR only when there is none; true always uses OCR; false never. One of: auto, true, false.
- `lang` (query): OCR languages, as in /v1/ocr: up to three of eng, hin, tam, tel, kan, mal, ben, guj, pan, ori, mar joined by +. Default eng.

Example: `POST` https://api.dharun.dev/v1/doc/invoice?utm_medium=docs-md

Response, with lists and long text shortened:

```json
{
  "data": {
    "title": "TAX INVOICE",
    "number": "INV-2026-0042",
    "date": "2026-10-14",
    "seller": {
      "name": "Sri Murugan Stores",
      "gstin": "33AABCS1429B1Z1",
      "state_code": "33",
      "state": "Tamil Nadu"
    },
    "buyer": {
      "name": "Kaveri Traders",
      "gstin": "29AAACK6812C1ZS",
      "state_code": "29",
      "state": "Karnataka"
    },
    "place_of_supply": "Karnataka (29)",
    "supply": "inter_state",
    "hsn": [
      "1006",
      "7323"
    ],
    "amounts": {
      "taxable_value": 11975,
      "cgst": null,
      "sgst": null,
      "igst": 1037.5,
      "cess": null,
      "round_off": 0.5,
      "total": 13013
    },
    "amount_in_words": "Thirteen thousand thirteen rupees only",
    "ifsc": "SBIN0000001",
    "upi": "srimurugan@okaxis",
    "checks": [
      {
        "name": "seller_gstin",
        "passed": true,
        "detail": "33AABCS1429B1Z1 is a valid GSTIN of Tamil Nadu."
      },
      {
        "name": "buyer_gstin",
        "passed": true,
        "detail": "29AAACK6812C1ZS is a valid GSTIN of Karnataka."
      },
      {
        "name": "total_adds_up",
        "passed": true,
        "detail": "Taxable value, taxes and round off add up to 13013.00; the total says 13013.00."
      }
    ],
    "method": "text",
    "all_checks_passed": true
  },
  "meta": {
    "note": "Fields are read by their labels, so an unusual layout can leave some empty (null). The checks test the invoice against itself and the GST rules; they do not ask…"
  }
}
```

## Read a GST e-invoice's signed QR code

`POST /v1/doc/einvoice-qr`

Finds the signed QR code that the Invoice Registration Portal puts on an e-invoice, in a picture or the first three pages of a PDF, and returns what it holds: IRN and its date, seller and buyer GSTINs, document number, type and date, total value, item count and main HSN code, with the GSTINs and the IRN's format checked. The signature is not verified here; the e-invoice portal's "Verify signed QR code" does that. The sample is shaped like one but not signed.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/einvoice-qr.png

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Example: `POST` https://api.dharun.dev/v1/doc/einvoice-qr?utm_medium=docs-md

Response, with lists and long text shortened:

```json
{
  "data": [
    {
      "irn": "c9dc0f70c4a4c97d98356aeeeffcf8cc3dc994364ced1875233a1d9f08bfbc8d",
      "irn_date": "2026-10-14 11:05:00",
      "seller_gstin": "33AABCS1429B1Z1",
      "buyer_gstin": "29AAACK6812C1ZS",
      "document_number": "INV-2026-0042",
      "document_type": "Tax invoice",
      "document_date": "2026-10-14",
      "total_value": 13013,
      "item_count": 2,
      "main_hsn": "1006",
      "issuer": "Sample, not issued by NIC",
      "signature": {
        "algorithm": "RS256",
        "key_id": "SAMPLE",
        "verified": false
      },
      "checks": [
        {
          "name": "seller_gstin",
          "passed": true,
          "detail": "33AABCS1429B1Z1 is a valid GSTIN."
        },
        {
          "name": "buyer_gstin",
          "passed": true,
          "detail": "29AAACK6812C1ZS is a valid GSTIN."
        },
        {
          "name": "irn_format",
          "passed": true,
          "detail": "The IRN is a SHA-256 hash: 64 hexadecimal characters. This one has 64."
        }
      ]
    }
  ],
  "meta": {
    "total": 1,
    "note": "The signature is not checked here (verified is false): the QR is signed by the Invoice Registration Portal, and the GST e-invoice portal's \"Verify signed QR cod…"
  }
}
```

## Mask an Aadhaar number in a picture

`POST /v1/doc/mask-aadhaar`

Returns the picture with the first eight digits of every Aadhaar number (and the first twelve of a 16-digit Virtual ID) covered in black, leaving the last four, as the UIDAI's masked Aadhaar does, and the QR code covered too, since it holds the full number. A number is four-digit groups that pass the Verhoeff check. Answers 422 when nothing is found, so an unmasked picture is never returned as if masked. X-Masked-Numbers and X-Masked-QR-Codes say what was covered. Check the result by eye before you share it. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A picture: JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF, up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/idcard.png

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `all` (query): true covers every digit. Default false (the last four stay). One of: true, false.
- `qr` (query): false leaves QR codes uncovered. Default true. One of: true, false.

Example: `POST` https://api.dharun.dev/v1/doc/mask-aadhaar?utm_medium=docs-md (returns the image itself)

## Read a shop receipt

`POST /v1/doc/receipt`

From a photo or PDF of a bill: the merchant (its first line), GSTIN, date, the lines that end in an amount, tax and total, the way it was paid when the bill says, and a check that the lines add up to the total. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/receipt.png

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `first` (query): PDF: first page. Default 1.
- `last` (query): PDF: last page. A PDF's own text is read up to 500 pages, a scan up to 10.
- `ocr` (query): PDF: auto (default) reads the PDF's own text and uses OCR only when there is none; true always uses OCR; false never. One of: auto, true, false.
- `lang` (query): OCR languages, as in /v1/ocr: up to three of eng, hin, tam, tel, kan, mal, ben, guj, pan, ori, mar joined by +. Default eng.

Example: `POST` https://api.dharun.dev/v1/doc/receipt?lang=eng%2Btam&utm_medium=docs-md

Response, with lists and long text shortened:

```json
{
  "data": {
    "merchant": "SRI MURUGAN STORES",
    "gstin": "33AABCS1429B1Z1",
    "date": "2026-10-14",
    "items": [
      {
        "name": "Basmati rice 5 kg",
        "amount": 575
      },
      {
        "name": "Toor dal 1 kg",
        "amount": 168
      },
      {
        "name": "Coconut oil 1 L",
        "amount": 245
      }
    ],
    "tax": null,
    "total": 1250,
    "payment": "upi",
    "checks": [
      {
        "name": "items_add_up",
        "passed": true,
        "detail": "The lines add up to 1250.00 (1250.00 with tax); the total says 1250.00."
      },
      {
        "name": "seller_gstin",
        "passed": true,
        "detail": "33AABCS1429B1Z1 is a valid GSTIN of Tamil Nadu."
      }
    ]
  },
  "meta": {
    "note": "Items are the lines above the total that end in an amount. A crumpled or faded bill reads worse: photograph it flat and straight. Read with OCR (Tesseract), so …"
  }
}
```

## Read a cheque

`POST /v1/doc/cheque`

From a photo of a cheque leaf: the IFSC, the MICR line (cheque number and nine-digit MICR code), the account number when printed with a label, and the date; the IFSC and MICR code are looked up in RBI's branch list and checked against each other. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/cheque.png

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `first` (query): PDF: first page. Default 1.
- `last` (query): PDF: last page. A PDF's own text is read up to 500 pages, a scan up to 10.
- `ocr` (query): PDF: auto (default) reads the PDF's own text and uses OCR only when there is none; true always uses OCR; false never. One of: auto, true, false.
- `lang` (query): OCR languages, as in /v1/ocr: up to three of eng, hin, tam, tel, kan, mal, ben, guj, pan, ori, mar joined by +. Default eng.

Example: `POST` https://api.dharun.dev/v1/doc/cheque?utm_medium=docs-md

Response, with lists and long text shortened:

```json
{
  "data": {
    "ifsc": "HDFC0000001",
    "micr": "400240003",
    "cheque_number": "000123",
    "account_number": "50100123456789",
    "date": "2026-10-14",
    "branch": {
      "address": "101-104 TULSIANI CHAMBERSFREE PRESS JOURNAL MARGNARIMAN POINTMUMBAI MAHARASHTRA 400 021",
      "bank": {
        "code": "HDFC",
        "name": "HDFC Bank"
      },
      "branch": "TULSIANI CHMBRS - NARIMAN PT",
      "centre": "Mumbai",
      "city": "Greater Mumbai",
      "contact": "+919890603333",
      "district": "Mumbai",
      "ifsc": "HDFC0000001",
      "imps": true,
      "micr": "400240003",
      "neft": true,
      "rtgs": true,
      "state": "Maharashtra",
      "stateCode": "MH",
      "swift": "HDFCINBB",
      "upi": true
    },
    "micr_branch": null,
    "checks": [
      {
        "name": "ifsc_known",
        "passed": true,
        "detail": "The IFSC HDFC0000001 is a branch in RBI's list."
      }
    ]
  },
  "meta": {
    "note": "The MICR line is printed in a magnetic font that ordinary OCR reads imperfectly; the MICR code is checked against RBI's branch list. This reads a cheque; it doe…"
  }
}
```

## Bank statement to transactions

`POST /v1/doc/statement`

Every transaction of a bank statement: date, description (lines that run on are joined), reference, debit, credit and balance, with each balance checked against the one before. Columns are found by the header row, so most banks' text PDFs work; a scan is read with OCR. format=csv returns a CSV file. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/statement.pdf

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `format` (query): json (default) or csv. One of: json, csv.
- `first` (query): PDF: first page. Default 1.
- `last` (query): PDF: last page. A PDF's own text is read up to 500 pages, a scan up to 10.
- `ocr` (query): PDF: auto (default) reads the PDF's own text and uses OCR only when there is none; true always uses OCR; false never. One of: auto, true, false.
- `lang` (query): OCR languages, as in /v1/ocr: up to three of eng, hin, tam, tel, kan, mal, ben, guj, pan, ori, mar joined by +. Default eng.

Example: `POST` https://api.dharun.dev/v1/doc/statement?utm_medium=docs-md

Response, with lists and long text shortened:

```json
{
  "data": [
    {
      "date": "2026-10-01",
      "description": "Opening balance",
      "debit": null,
      "credit": null,
      "balance": 25000,
      "balance_ok": null
    },
    {
      "date": "2026-10-02",
      "description": "UPI-SRI MURUGAN STORES- srimurugan@okaxis",
      "reference": "627512345678",
      "debit": 1250,
      "credit": null,
      "balance": 23750,
      "balance_ok": true
    },
    {
      "date": "2026-10-05",
      "description": "NEFT CR-KAVERI TRADERS- SALARY OCT 2026",
      "reference": "N278261234567",
      "debit": null,
      "credit": 45000,
      "balance": 68750,
      "balance_ok": true
    }
  ],
  "meta": {
    "total": 7,
    "note": "Columns are found by the header row (date, narration, reference, withdrawals or debits, deposits or credits, balance). balance_ok checks each balance against th…"
  }
}
```

## Redact a PDF or picture

`POST /v1/doc/redact`

Finds the identifiers that /v1/text/redact finds (Aadhaar, PAN, GSTIN, phone numbers, email addresses, card and account numbers, UPI handles and more) in a PDF or picture and returns it with each covered in black. A PDF comes back as pictures of its pages, so no text survives under a box. X-Redacted counts what was covered by type; 422 when nothing was found. Names are not found; check the result before you share it. Options go in the query string, or as form fields when the file is sent as multipart/form-data.

Send: A PDF, or a picture of a page (JPEG, PNG, WebP, HEIC, AVIF, GIF, BMP or TIFF), up to 10 MB. Post the file as the request body, or as `multipart/form-data`. Sample file: https://api.dharun.dev/samples/statement.pdf

Model endpoint: it will need a free API key (sent as `Authorization: Bearer <key>` or `X-API-Key`). Until the date on https://api.dharun.dev/keys it answers without one and says so in the `X-Key-Notice` header. With a key: 500 model requests a day.

What you send is never written to the log, and the answer is not cached.

Parameters:

- `types` (query): Which kinds to cover, comma-separated, as in /v1/text/redact. Default all.
- `first` (query): PDF: first page. Default 1.
- `last` (query): PDF: last page. A PDF's own text is read up to 500 pages, a scan up to 10.
- `ocr` (query): PDF: auto (default) reads the PDF's own text and uses OCR only when there is none; true always uses OCR; false never. One of: auto, true, false.
- `lang` (query): OCR languages, as in /v1/ocr: up to three of eng, hin, tam, tel, kan, mal, ben, guj, pan, ori, mar joined by +. Default eng.

Example: `POST` https://api.dharun.dev/v1/doc/redact?utm_medium=docs-md (returns the PDF itself)
