HTML ke PDF API: Cara Membuat Invoice PDF Otomatis dari Template HTML

Oleh , Tim Engineering APIPedia · Terbit · 11 menit baca

API HTML ke PDF APIPedia mengubah template HTML jadi invoice PDF lewat satu request POST /v1/pdf/html. Ada contoh curl, Node.js, dan Python.

Sampul APIPedia: judul "Invoice PDF otomatis dari satu request HTML" dengan chip POST /v1/pdf/html dan kartu fakta HTML to PDF v2.0.0: maksimal HTML 2 MB, 100 halaman, input html atau url, output application/pdf atau base64, teks bisa diseleksi dan dicari.

API HTML ke PDF APIPedia mengubah template HTML menjadi berkas PDF lewat satu request POST /v1/pdf/html, sehingga invoice bisa dibuat otomatis setiap kali pesanan dibayar. Anda mengirim HTML maksimal 2 MB, memilih ukuran kertas dan margin, lalu menerima PDF yang teksnya bisa diseleksi dan dicari.

Tutorial ini memandu Anda membuat invoice PDF dari data pesanan: menyusun template yang cocok dengan mesin render, mengirim request dengan curl, Node.js, dan Python, membaca respons, menambahkan nomor halaman, dan menghindari kesalahan yang paling sering terjadi. Semua contoh memakai kunci apk_xxx dan data fiktif.

Ringkasan cepat: apa yang perlu Anda tahu?

API HTML ke PDF punya satu endpoint, menerima JSON, dan mengembalikan berkas PDF atau base64. Tabel berikut merangkum hal yang paling sering dicari.

ItemDetail
EndpointPOST https://api.apipedia.id/v1/pdf/html
AutentikasiHeader Authorization: Bearer apk_xxx
InputTepat satu dari html (maks. 2 MB) atau url (alamat http/https publik)
Ukuran kertasA4 (default), Letter, Legal, A3, A5; landscape untuk orientasi lanskap
MarginObjek top, right, bottom, left dalam px, in, cm, atau mm; default 10mm
OutputBerkas application/pdf, atau JSON berisi base64 lewat output=base64
BatasRender 20 detik, 100 halaman, PDF maksimal 20 MB
Cakupan CSSStyle inline dan blok <style>; flexbox, CSS Grid, stylesheet eksternal, dan JavaScript tidak didukung

Masalah apa yang diselesaikan API HTML ke PDF?

API ini menghilangkan kebutuhan memasang dan merawat mesin pembuat PDF di server Anda sendiri. Toko online, aplikasi kasir, dan sistem langganan hampir selalu perlu mengirim invoice atau kuitansi. Membuatnya dengan tangan tidak mungkin, sedangkan menjalankan pustaka PDF sendiri berarti mengurus font, memori, dan antrean render.

Dengan API, backend Anda cukup menyusun HTML dari data pesanan dan mengirimnya dalam satu request. Hasilnya dibangun dari teks, bukan tangkapan layar, jadi nomor invoice dan nama pelanggan tetap bisa dicari di dalam PDF.

Diagram alur invoice otomatis dalam empat langkah: 1) pesanan dibayar, ambil nomor INV-2026-0042, 3 baris item, dan total Rp 393.000 dari database; 2) susun HTML memakai tabel dan blok style di dalam dokumen; 3) kirim POST /v1/pdf/html dengan header Authorization Bearer apk_xxx serta field html, format, margin, dan filename; 4) terima application/pdf atau base64 dalam JSON, lalu simpan atau kirim. Catatan: simpan PDF di storage sendiri agar tidak render ulang, batas 20 detik per render, 100 halaman, hasil maksimal 20 MB, dan saat menerima status 503 ulangi setelah nilai Retry-After.

Apa yang perlu disiapkan sebelum mulai?

Anda hanya butuh satu kunci API APIPedia. Daftar atau masuk di console.apipedia.id, buka menu Token API, lalu buat kunci baru. Kunci berawalan apk_ dan dikirim lewat header Authorization: Bearer apk_xxx.

Simpan kunci di variabel lingkungan server, misalnya APIPEDIA_KEY. Jangan menaruhnya di kode yang berjalan di browser, karena siapa pun bisa membacanya. Produk HTML to PDF tercatat di tier Free pada katalog, jadi Anda bisa mencoba tanpa biaya.

Langkah 1: bagaimana menyusun template invoice yang aman dirender?

Template yang aman memakai tabel untuk tata letak dan menaruh semua CSS di blok <style> atau atribut style. Mesin render API ini bukan browser. Flexbox, CSS Grid, dan <link rel="stylesheet"> tidak dibaca, dan JavaScript tidak pernah dijalankan.

Simpan contoh berikut sebagai invoice.html. Bagian kop memakai tabel dua kolom sebagai pengganti flexbox, dan kolom angka diratakan ke kanan dengan kelas .r.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Invoice INV-2026-0042</title>
<style>
  body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; color: #1c2a23; }
  h1 { font-size: 22px; color: #2e7d5b; margin: 0 0 4px; }
  table { width: 100%; border-collapse: collapse; }
  th { background: #e3f1e8; text-align: left; padding: 8px; border-bottom: 1px solid #2e7d5b; }
  td { padding: 8px; border-bottom: 1px solid #e6e2d8; }
  .r { text-align: right; }
  .polos td { border: 0; padding: 0 0 16px; }
  .total td { font-weight: bold; border-top: 2px solid #1c2a23; }
</style>
</head>
<body>
  <table class="polos">
    <tr>
      <td><h1>INVOICE</h1>Toko Contoh Sejahtera<br>Jl. Contoh No. 1, Bandung</td>
      <td class="r">No: INV-2026-0042<br>Tanggal: 12 November 2026<br>Jatuh tempo: 19 November 2026</td>
    </tr>
  </table>
  <p>Ditagihkan kepada: <strong>PT Pelanggan Fiktif</strong></p>
  <table>
    <thead>
      <tr><th>Deskripsi</th><th class="r">Qty</th><th class="r">Harga</th><th class="r">Subtotal</th></tr>
    </thead>
    <tbody>
      <tr><td>Kaos polos hitam L</td><td class="r">3</td><td class="r">Rp 85.000</td><td class="r">Rp 255.000</td></tr>
      <tr><td>Topi kanvas</td><td class="r">2</td><td class="r">Rp 60.000</td><td class="r">Rp 120.000</td></tr>
      <tr><td>Ongkos kirim</td><td class="r">1</td><td class="r">Rp 18.000</td><td class="r">Rp 18.000</td></tr>
      <tr class="total"><td colspan="3" class="r">Total</td><td class="r">Rp 393.000</td></tr>
    </tbody>
  </table>
</body>
</html>

Perbandingan cakupan CSS mesin render HTML to PDF. Didukung: style inline dan blok style untuk warna, border, font, margin, perataan, lebar, dan tinggi; tabel untuk tata letak dan daftar item; gambar JPEG atau PNG dari https publik, maksimal 5 MB per gambar dan 50 gambar; header dan footer dengan {page} dan {pages}. Tidak didukung: flexbox dan CSS Grid, stylesheet eksternal lewat link rel=stylesheet, JavaScript halaman yang menghasilkan PDF kosong, dan gambar dari alamat privat atau internal. Field javascript, scale, dan printBackground diabaikan.

Logo harus berupa JPEG atau PNG yang bisa diakses lewat https publik. Batasnya 5 MB per gambar dan 50 gambar per dokumen. Gambar yang gagal dimuat dilewati tanpa menggagalkan render, jadi periksa hasil PDF pertama Anda untuk memastikan logo benar-benar muncul.

Langkah 2: bagaimana mengirim HTML ke API dengan curl?

Kirim isi berkas HTML sebagai field html dalam body JSON, lalu simpan respons sebagai berkas PDF. Perintah di bawah memakai jq untuk membungkus invoice.html menjadi JSON yang valid, sehingga tanda kutip di dalam HTML tidak perlu di-escape manual.

jq -n --rawfile html invoice.html '{
  html: $html,
  format: "A4",
  margin: { top: "20mm", bottom: "20mm", left: "15mm", right: "15mm" },
  filename: "invoice-INV-2026-0042.pdf"
}' | curl -s -X POST https://api.apipedia.id/v1/pdf/html \
  -H "Authorization: Bearer apk_xxx" \
  -H "Content-Type: application/json" \
  -d @- -o invoice-INV-2026-0042.pdf

Secara default, respons berupa berkas application/pdf dengan header Content-Disposition: attachment. Buka invoice-INV-2026-0042.pdf untuk melihat hasilnya.

Parameter body yang tersedia

FieldTipeKeterangan
htmlstringDokumen atau potongan HTML, maksimal 2 MB. Isi ini atau url, tidak keduanya.
urlstringAlamat http/https publik yang akan dirender. Alamat privat dan internal ditolak.
formatstringA4 (default), Letter, Legal, A3, atau A5.
landscapebooleanOrientasi lanskap.
marginobjecttop, right, bottom, left dengan satuan px, in, cm, atau mm. Default 10mm.
filenamestringNama berkas hasil unduhan.
displayHeaderFooterbooleanMengaktifkan header dan footer.
headerTemplate, footerTemplatestringIsi header dan footer, maksimal 20 KB masing-masing.
outputstringpdf (default) atau base64. Bisa juga dikirim sebagai query ?output=base64.

Langkah 3: bagaimana membuat invoice dari data pesanan di Node.js?

Di Node.js, susun HTML dari objek pesanan, kirim dengan fetch, lalu tulis respons biner ke berkas. Contoh ini berjalan di Node.js 18 ke atas tanpa dependensi tambahan. Fungsi esc meng-escape teks dari pengguna agar nama produk yang mengandung tanda < tidak merusak template.

import { writeFile } from "node:fs/promises";

const API = "https://api.apipedia.id/v1/pdf/html";
const KEY = process.env.APIPEDIA_KEY; // apk_xxx

const rupiah = (n) => "Rp " + n.toLocaleString("id-ID");
const esc = (s) =>
  String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);

function invoiceHtml(order) {
  const baris = order.item
    .map(
      (i) => `<tr><td>${esc(i.nama)}</td><td class="r">${i.qty}</td>
        <td class="r">${rupiah(i.harga)}</td><td class="r">${rupiah(i.qty * i.harga)}</td></tr>`
    )
    .join("");
  const total = order.item.reduce((n, i) => n + i.qty * i.harga, 0);

  return `<!doctype html><html><head><meta charset="utf-8"><style>
    body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; color: #1c2a23; }
    table { width: 100%; border-collapse: collapse; }
    th { background: #e3f1e8; text-align: left; padding: 8px; }
    td { padding: 8px; border-bottom: 1px solid #e6e2d8; }
    .r { text-align: right; }
    .total td { font-weight: bold; border-top: 2px solid #1c2a23; }
  </style></head><body>
    <h1>INVOICE ${esc(order.nomor)}</h1>
    <p>Ditagihkan kepada: <strong>${esc(order.pembeli)}</strong></p>
    <table>
      <thead><tr><th>Deskripsi</th><th class="r">Qty</th><th class="r">Harga</th><th class="r">Subtotal</th></tr></thead>
      <tbody>${baris}
        <tr class="total"><td colspan="3" class="r">Total</td><td class="r">${rupiah(total)}</td></tr>
      </tbody>
    </table>
  </body></html>`;
}

export async function buatInvoicePdf(order) {
  const filename = `invoice-${order.nomor}.pdf`;
  const res = await fetch(API, {
    method: "POST",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
    body: JSON.stringify({
      html: invoiceHtml(order),
      format: "A4",
      margin: { top: "20mm", bottom: "20mm", left: "15mm", right: "15mm" },
      filename,
    }),
  });

  if (!res.ok) {
    const err = await res.json().catch(() => ({}));
    throw new Error(`HTTP ${res.status}: ${err.message ?? "gagal membuat PDF"}`);
  }

  await writeFile(filename, Buffer.from(await res.arrayBuffer()));
  return filename;
}

const berkas = await buatInvoicePdf({
  nomor: "INV-2026-0042",
  pembeli: "PT Pelanggan Fiktif",
  item: [
    { nama: "Kaos polos hitam L", qty: 3, harga: 85000 },
    { nama: "Topi kanvas", qty: 2, harga: 60000 },
    { nama: "Ongkos kirim", qty: 1, harga: 18000 },
  ],
});
console.log("Tersimpan:", berkas);

Simpan sebagai invoice.mjs, lalu jalankan dengan APIPEDIA_KEY=apk_xxx node invoice.mjs. Dalam aplikasi nyata, panggil buatInvoicePdf dari handler webhook pembayaran atau dari antrean tugas, bukan dari request halaman.

Langkah 4: bagaimana menerima PDF sebagai base64 di Python?

Tambahkan output=base64 agar respons berupa amplop JSON berisi PDF dalam base64. Bentuk ini praktis bila Anda ingin menyimpan hasil ke database, mengunggahnya ke object storage, atau melampirkannya ke email tanpa menulis berkas sementara.

import base64
import os
from html import escape

import requests  # pip install requests

API = "https://api.apipedia.id/v1/pdf/html"
KEY = os.environ["APIPEDIA_KEY"]  # apk_xxx

CSS = """
body { font-family: Helvetica, Arial, sans-serif; font-size: 12px; color: #1c2a23; }
table { width: 100%; border-collapse: collapse; }
th { background: #e3f1e8; text-align: left; padding: 8px; }
td { padding: 8px; border-bottom: 1px solid #e6e2d8; }
.r { text-align: right; }
.total td { font-weight: bold; border-top: 2px solid #1c2a23; }
"""


def rupiah(n):
    return "Rp " + f"{n:,}".replace(",", ".")


def invoice_html(order):
    baris = "".join(
        f"<tr><td>{escape(i['nama'])}</td><td class='r'>{i['qty']}</td>"
        f"<td class='r'>{rupiah(i['harga'])}</td>"
        f"<td class='r'>{rupiah(i['qty'] * i['harga'])}</td></tr>"
        for i in order["item"]
    )
    total = sum(i["qty"] * i["harga"] for i in order["item"])
    return (
        "<!doctype html><html><head><meta charset='utf-8'><style>" + CSS + "</style></head><body>"
        f"<h1>INVOICE {escape(order['nomor'])}</h1>"
        f"<p>Ditagihkan kepada: <strong>{escape(order['pembeli'])}</strong></p>"
        "<table><thead><tr><th>Deskripsi</th><th class='r'>Qty</th>"
        "<th class='r'>Harga</th><th class='r'>Subtotal</th></tr></thead><tbody>"
        + baris
        + f"<tr class='total'><td colspan='3' class='r'>Total</td><td class='r'>{rupiah(total)}</td></tr>"
        "</tbody></table></body></html>"
    )


def buat_invoice_pdf(order):
    res = requests.post(
        API,
        params={"output": "base64"},
        headers={"Authorization": f"Bearer {KEY}"},
        json={
            "html": invoice_html(order),
            "format": "A4",
            "margin": {"top": "20mm", "bottom": "20mm", "left": "15mm", "right": "15mm"},
            "filename": f"invoice-{order['nomor']}.pdf",
        },
        timeout=30,
    )
    body = res.json()
    if not body.get("is_success"):
        raise RuntimeError(f"HTTP {res.status_code}: {body.get('message')}")

    data = body["data"]
    with open(data["filename"], "wb") as f:
        f.write(base64.b64decode(data["base64"]))
    return data["filename"], data["size"]


if __name__ == "__main__":
    nama, ukuran = buat_invoice_pdf({
        "nomor": "INV-2026-0042",
        "pembeli": "PT Pelanggan Fiktif",
        "item": [
            {"nama": "Kaos polos hitam L", "qty": 3, "harga": 85000},
            {"nama": "Topi kanvas", "qty": 2, "harga": 60000},
            {"nama": "Ongkos kirim", "qty": 1, "harga": 18000},
        ],
    })
    print(f"Tersimpan: {nama} ({ukuran} byte)")

Seperti apa contoh respons API HTML ke PDF?

Dengan output=base64, respons memakai amplop standar APIPedia { is_success, message, data }. Berikut respons nyata dari template invoice di atas, dengan isi base64 dipotong:

{
  "is_success": true,
  "message": "PDF berhasil dibuat",
  "data": {
    "filename": "invoice-INV-2026-0042.pdf",
    "size": 20304,
    "maxPages": 100,
    "base64": "JVBERi0xLjMKJf////8KOCAwIG9iago8PAov..."
  }
}
FieldArti
filenameNama berkas sesuai field filename yang Anda kirim.
sizeUkuran PDF dalam byte. Invoice satu halaman di contoh ini berukuran 20.304 byte.
maxPagesBatas halaman per render, yaitu 100. Halaman di luar batas dipotong.
base64Isi PDF. Decode menjadi byte sebelum disimpan atau dikirim.

Tanpa output=base64, tidak ada amplop JSON pada respons sukses. Isi respons langsung berupa byte PDF, jadi cek status HTTP sebelum menulisnya ke berkas.

Set displayHeaderFooter ke true, lalu tulis {page} dan {pages} di footerTemplate. Kedua penanda itu diganti otomatis dengan nomor halaman saat ini dan jumlah halaman. Ini berguna untuk invoice dengan banyak baris atau laporan bulanan.

{
  "html": "<h1>Laporan Penjualan Oktober 2026</h1>...",
  "format": "A4",
  "margin": { "top": "25mm", "bottom": "20mm", "left": "15mm", "right": "15mm" },
  "displayHeaderFooter": true,
  "headerTemplate": "Toko Contoh Sejahtera",
  "footerTemplate": "Halaman {page} dari {pages}"
}

Beri margin atas dan bawah yang cukup agar header dan footer tidak menimpa isi dokumen.

Kesalahan umum apa yang perlu dihindari?

Sebagian besar masalah berasal dari template yang dirancang untuk browser, bukan dari API-nya. Periksa daftar berikut sebelum membuka tiket dukungan.

  • Tata letak bergeser. Template memakai flexbox atau CSS Grid. Ganti dengan tabel.
  • PDF tanpa gaya. CSS dimuat lewat <link rel="stylesheet">. Pindahkan aturannya ke blok <style>.
  • PDF kosong dari url. Halaman itu digambar oleh JavaScript, sedangkan JavaScript tidak dijalankan. Kirim HTML jadi lewat field html.
  • Logo hilang. Gambar berformat SVG atau WebP, atau berada di alamat internal. Pakai JPEG atau PNG di https publik.
  • Mengirim html dan url sekaligus. Isi tepat satu.
  • Mengandalkan javascript, scale, atau printBackground. Ketiga field ini diabaikan.
  • Render ulang setiap kali diunduh. Simpan PDF sekali di storage Anda, lalu sajikan dari sana.
  • Data pengguna tidak di-escape. Nama atau catatan pembeli bisa merusak HTML. Selalu escape sebelum disisipkan.

Kode status yang perlu ditangani

StatusArtiTindakan
400Input tidak valid, URL privat, atau halaman gagal diambilBaca message, yang menyebut field bermasalah
401Token tidak ada, tidak dikenal, atau kedaluwarsaPeriksa header Authorization
403Kunci tidak punya scope untuk endpoint iniBuat kunci dengan scope yang sesuai
503Mesin PDF sibuk, antrean penuhUlangi setelah nilai header Retry-After
504Render melebihi 20 detikKecilkan dokumen atau kurangi gambar

Pertanyaan yang sering diajukan

Apakah API HTML ke PDF APIPedia gratis?

Produk HTML to PDF tercatat di tier Free pada katalog APIPedia. Anda cukup membuat kunci di menu Token API pada konsol untuk mulai mencoba. Rincian kuota tiap paket ada di halaman paket konsol.

Apakah teks di PDF bisa diseleksi dan dicari?

Bisa. PDF dibangun dari teks, bukan dari tangkapan layar halaman. Nomor invoice, nama pelanggan, dan nominal tetap bisa disalin dan dicari.

Kenapa hasil PDF berbeda dari tampilan di browser?

Mesin render ini bukan browser. Ia membaca style inline dan blok <style> untuk properti umum seperti warna, border, font, margin, perataan, lebar, dan tinggi. Tata letak rumit tetap dirender, tetapi bisa bergeser dari tampilan aslinya.

Berapa ukuran maksimal dokumen yang bisa dirender?

HTML maksimal 2 MB, hasil maksimal 100 halaman dan 20 MB, dengan waktu render 20 detik. Untuk laporan yang lebih panjang, pecah menjadi beberapa PDF per periode.

Bisakah asisten AI membuat PDF lewat API ini?

Bisa. Server MCP APIPedia menyediakan tool html_to_pdf yang memanggil endpoint yang sama. Cara menyambungkannya ada di panduan menghubungkan APIPedia ke Claude lewat MCP.

Langkah berikutnya: coba di konsol

Invoice otomatis cukup butuh satu template HTML berbasis tabel dan satu request POST /v1/pdf/html. Buat kunci di console.apipedia.id, jalankan contoh curl di atas dengan invoice.html, lalu pindahkan logikanya ke backend Anda.

Untuk melengkapi alur toko online, isi baris ongkos kirim dengan estimasi dari API cek ongkir, dan hitung tanggal jatuh tempo pada hari kerja dengan API hari libur nasional.

Topik:

${c.about}

Tim APIPedia — Tim Engineering APIPedia

Tim engineering di balik APIPedia yang membangun dan memelihara API data Indonesia - ongkir, OCR KTP, data wilayah, BMKG, hingga server MCP - dan menulis panduan integrasinya dari pengalaman langsung.