Đọc · 8 phút Cập nhật 30/04/2026 Cấp độ · Trung cấp

OCR hoá đơn tiếng Việt

Trích xuất văn bản từ ảnh, PDF — hỗ trợ đầy đủ dấu tiếng Việt, ngôn ngữ chữ ký, dấu mộc đỏ. Đặc biệt tối ưu cho hoá đơn VAT mẫu Bộ Tài chính, hoá đơn điện tử và biên lai bán hàng.

Hai endpoint chính

EndpointĐịnh dạng đầu vàoUse case
POST /api/v1/ocr/imageJPG, PNG, HEIC, WebP — tối đa 10 MBẢnh chụp hoá đơn từ điện thoại
POST /api/v1/ocr/pdfPDF — tối đa 50 trang, 25 MBHoá đơn điện tử PDF từ Misa, Viettel, FPT

OCR ảnh — chi tiết

Cú pháp cURL cơ bản

bashcurl -X POST "https://zenicloud.io/api/v1/ocr/image?ws=prod" \
  -H "Authorization: Bearer $ZENI_TOKEN" \
  -H "Content-Type: image/jpeg" \
  --data-binary "@hoa_don_dien.jpg"

Response trả về văn bản đã được sắp xếp theo thứ tự đọc (top-down, left-right) cùng bounding box từng dòng:

json{
  "text": "CÔNG TY CỔ PHẦN ĐIỆN LỰC TPHCM\nHOÁ ĐƠN GIÁ TRỊ GIA TĂNG\nMẫu số: 01GTKT3/001\nKý hiệu: AA/26E\nSố: 0000123\nNgày 15 tháng 04 năm 2026\nMST: 0301234567\nĐơn vị mua hàng: CTY TNHH ABC\nMST người mua: 0317999888\nTổng cộng tiền thanh toán: 1.250.000 VND",
  "language": "vi",
  "confidence": 0.96,
  "lines": [
    {
      "text": "CÔNG TY CỔ PHẦN ĐIỆN LỰC TPHCM",
      "bbox": [120, 45, 580, 78],
      "confidence": 0.98
    }
  ],
  "page_count": 1,
  "processing_time_ms": 2340,
  "cost_usd": 0.0015
}

Ví dụ Python — xử lý ảnh hoá đơn

pythonimport os, requests, re

ZENI = "https://zenicloud.io/api/v1"
TOKEN = os.environ["ZENI_TOKEN"]

def ocr_image(path):
    with open(path, "rb") as f:
        r = requests.post(
            f"{ZENI}/ocr/image?ws=prod",
            headers={
                "Authorization": f"Bearer {TOKEN}",
                "Content-Type": "image/jpeg",
            },
            data=f.read(),
            timeout=60,
        )
    r.raise_for_status()
    return r.json()

result = ocr_image("hoa_don_dien.jpg")
print(result["text"])

Trích xuất trường hoá đơn VAT

Sau khi có text, dùng regex hoặc chain với AI để rút field. Cách tiếp cận khuyến nghị: gọi tiếp ZeniRouter với task_type: "extract_invoice_vn" để nhận về JSON cấu trúc.

pythonimport json

def extract_invoice(text):
    """Chain OCR text vào AI để parse field."""
    r = requests.post(
        f"{ZENI}/router/complete?ws=prod",
        headers={"Authorization": f"Bearer {TOKEN}"},
        json={
            "messages": [{
                "role": "user",
                "content": (
                    "Trích xuất các trường sau từ hoá đơn VN dưới dạng JSON:\n"
                    "so_hoa_don, ky_hieu, ngay (YYYY-MM-DD), "
                    "mst_ban, ten_ban, mst_mua, ten_mua, "
                    "tong_cong_truoc_thue, thue_vat, tong_thanh_toan.\n\n"
                    f"VĂN BẢN:\n{text}"
                )
            }],
            "task_type": "extract_invoice_vn",
            "response_format": "json",
        },
        timeout=60,
    )
    return json.loads(r.json()["text"])

ocr = ocr_image("hoa_don_dien.jpg")
fields = extract_invoice(ocr["text"])
print(fields)
# {
#   "so_hoa_don": "0000123",
#   "ky_hieu": "AA/26E",
#   "ngay": "2026-04-15",
#   "mst_ban": "0301234567",
#   "ten_ban": "CTY CP ĐIỆN LỰC TPHCM",
#   "mst_mua": "0317999888",
#   "ten_mua": "CTY TNHH ABC",
#   "tong_cong_truoc_thue": 1136364,
#   "thue_vat": 113636,
#   "tong_thanh_toan": 1250000
# }

OCR PDF — Multi-page

PDF nhiều trang sẽ được xử lý song song. Response trả về mảng pages với text từng trang:

bashcurl -X POST "https://zenicloud.io/api/v1/ocr/pdf?ws=prod" \
  -H "Authorization: Bearer $ZENI_TOKEN" \
  -H "Content-Type: application/pdf" \
  --data-binary "@bao_cao_quy.pdf"
json{
  "pages": [
    {"page": 1, "text": "BÁO CÁO TÀI CHÍNH...", "confidence": 0.97},
    {"page": 2, "text": "Bảng cân đối kế toán...", "confidence": 0.95}
  ],
  "total_text": "BÁO CÁO TÀI CHÍNH... Bảng cân đối kế toán...",
  "page_count": 12,
  "language": "vi",
  "processing_time_ms": 8420,
  "cost_usd": 0.018
}

Tính năng riêng cho tiếng Việt

Xử lý batch — nhiều file một lúc

Khi cần xử lý hàng loạt (ví dụ 1000 hoá đơn cuối tháng), dùng async API:

pythonimport asyncio, aiohttp

async def ocr_one(session, path):
    with open(path, "rb") as f:
        async with session.post(
            f"{ZENI}/ocr/image?ws=prod",
            data=f.read(),
            headers={"Authorization": f"Bearer {TOKEN}"},
        ) as r:
            return await r.json()

async def ocr_batch(paths, concurrency=8):
    sem = asyncio.Semaphore(concurrency)
    async with aiohttp.ClientSession() as session:
        async def bounded(p):
            async with sem:
                return await ocr_one(session, p)
        return await asyncio.gather(*[bounded(p) for p in paths])

# 100 file song song concurrency 8
files = [f"invoices/{i}.jpg" for i in range(100)]
results = asyncio.run(ocr_batch(files))
print(f"Đã xử lý {len(results)} hoá đơn")

Chi phí OCR

LoạiĐơn giáGhi chú
OCR ảnh$1.50 / 1.000 ảnh~36.000 VND/1.000 ảnh
OCR PDF$1.50 / 1.000 trangPDF 10 trang = 10 trang đếm
Free tier500 ảnh/tháng miễn phíReset đầu tháng
Tối ưu chi phí
Pre-process ảnh xuống 1024px chiều dài tối đa trước khi gửi — chất lượng OCR không giảm rõ rệt nhưng tốc độ và cost tốt hơn 30 phần trăm. Đồng thời nén JPEG 80 phần trăm thay vì PNG.

Lỗi thường gặp

Nguyên nhânCách sửa
413File quá lớnGiảm dưới 10MB ảnh / 25MB PDF
415Định dạng không hỗ trợConvert sang JPG/PNG/PDF
422Ảnh quá mờ, confidence dưới 0.5Chụp lại ảnh ánh sáng tốt

Kết hợp với module khác