Bỏ qua, tới nội dung
Tài liệu APIv1

O3O DocBuilder và chuyển đổi

Điền mẫu

POST /v1/template/render điền dữ liệu JSON vào mẫu docx hoặc odt do người dùng tự soạn: trường {{ten}}, đường dẫn lồng nhau, lặp hàng bảng; trả docx, odt hoặc PDF. Chỉ có ở bản doanh nghiệp.

Trong trang này

Bộ phận nghiệp vụ soạn mẫu bằng trình soạn thảo quen thuộc, đặt các thẻ {{ten_truong}} vào chỗ cần điền; hệ thống của bạn chỉ gửi dữ liệu JSON. Đổi câu chữ hay bố cục của mẫu không cần sửa mã.

POST/v1/template/render

Điền dữ liệu JSON vào mẫu docx hoặc odt.

Xác thực: BearerCộng đồng (không có ở gói Cộng đồng)Doanh nghiệp

Soạn mẫu#

Mẫu là tệp docx hoặc odt soạn bình thường. Chỗ cần điền gõ thẳng dưới dạng chữ trong cặp ngoặc nhọn kép. Thay thế áp cho thân văn bản, bảng, đầu trang và chân trang; định dạng chữ tại vị trí đặt thẻ (font, cỡ, đậm, màu) được giữ nguyên.

Cú phápÝ nghĩa
{{ten}}Thay bằng data.ten. Tên trường khớp [A-Za-z_][A-Za-z0-9_]*; cho phép khoảng trắng sát hai dấu ngoặc, ví dụ {{ ten }}.
{{khach.ten}}Đi theo đường dẫn vào đối tượng lồng nhau.
{{#hang}}{{/hang}}Lặp hàng bảng theo mảng data.hang, xem mục dưới.
{{ten}} trong hàng lặpTra trong phần tử hiện tại trước, không có thì tra ở gốc data.
{{@index}}Số thứ tự trong hàng lặp, bắt đầu từ 1.

Giá trị được in thế nào#

Giá trị trong dataIn ra
ChuỗiGiữ nguyên. Ký tự \n thành ngắt dòng trong đoạn.
Số nguyênKhông có phần thập phân: 20.
Số thựcTối đa 6 chữ số có nghĩa.
true, false, Không.
nullChuỗi rỗng.
Mảng hoặc đối tượng ở vị trí trường đơnLỗi 422 template_error.

Lặp hàng bảng#

Đặt {{#hang}} ở ô ĐẦU và {{/hang}} ở ô CUỐI của CÙNG một hàng bảng. Hàng đó được nhân bản một lần cho mỗi phần tử của mảng data.hang, hai thẻ bị xoá; mảng rỗng thì hàng mẫu bị xoá. Hàng tiêu đề và các hàng khác của bảng giữ nguyên.

STTTên hàng hoá, dịch vụSố lượngThành tiền
{{#hang}}{{@index}}{{ten}}{{so_luong}}{{thanh_tien}}{{/hang}}

Trường thiếu#

options.missingTrường không có trong data
emptyMặc định. Thay bằng chuỗi rỗng.
keepĐể nguyên thẻ trong tài liệu, tiện khi soát mẫu.
errorTrả 422 template_error, detail.fields liệt kê trường thiếu.

Mẫu có macro bị từ chối#

Điền mẫu đọc và ghi thẳng các phần XML của tệp mẫu, không nạp mẫu qua LibreOffice; các phần khác của tệp được chép nguyên sang tệp kết quả, nên nếu nhận mẫu có macro thì macro sẽ nằm nguyên trong tệp kết quả. Vì vậy DocBuilder từ chối mọi mẫu có macro, kể cả tệp docm đã đổi đuôi thành docx, và trả 422 macro_not_allowed kèm thông điệp tiếng Việt. Mẫu bị coi là có macro khi:

  • Tệp OOXML (docx) có phần vbaProject.bin, khai kiểu nội dung macroEnabled trong [Content_Types].xml, hoặc có quan hệ trỏ tới phần VBA (kể cả khi phần đó bị đổi tên).
  • Tệp ODF (odt) có thư mục Basic/ hoặc Scripts/.

Mẫu được kiểm ngay khi nhận, trước khi tạo job, nên lỗi trả về ngay cả khi async = true. detail.reason cho biết lý do (vba_project, macro_enabled_content_type, odf_basic, odf_scripts); detail.part là tên phần gây lỗi trong tệp.

Tham số#

Thân multipart/form-data

  • templatefilebắt buộc
    Tệp mẫu docx hoặc odt.
  • datastring (JSON)bắt buộc
    Dữ liệu, gửi dạng CHUỖI JSON; gốc phải là đối tượng.
  • todocx | odt | pdftuỳ chọnMặc định: định dạng của mẫu
    Định dạng đích.
  • filenamestring, ≤ 255tuỳ chọn
    Tên tệp kết quả.
  • optionsstring (JSON)tuỳ chọn
    Ví dụ {"missing": "error"}.
  • asyncbooleantuỳ chọnMặc định: false
    true: trả ngay 202 kèm job.
  • callback_urlstring (URL)tuỳ chọn
    Cần tính năng callback trong token.

Thân application/json

  • template_urlURL, ≤ 2048bắt buộc
    URL tải mẫu, chịu quy tắc chống SSRF.
  • dataobjectbắt buộc
    Dữ liệu điền vào mẫu.
  • todocx | odt | pdftuỳ chọnMặc định: định dạng của mẫu
    Định dạng đích.
  • filenamestring, ≤ 255tuỳ chọn
    Tên tệp kết quả.
  • optionsobjecttuỳ chọn
    Tuỳ chọn điền mẫu.
  • asyncbooleantuỳ chọnMặc định: false
    true: trả ngay 202 kèm job.
  • callback_urlstring (URL)tuỳ chọn
    Cần tính năng callback trong token.

Đối tượng options

  • missingempty | keep | errortuỳ chọnMặc định: empty
    Cách xử lý trường thiếu.

Ví dụ: hợp đồng dịch vụ#

Mẫu hop-dong-mau.docx gồm các đoạn dưới đây (cột trái là chữ gõ trong mẫu, cột phải là kết quả sau khi điền dữ liệu bên dưới).

Gõ trong mẫuKết quả
Số: {{so_hop_dong}}Số: HD-2026-091
Hôm nay, ngày {{ngay_ky}}, tại {{dia_diem}}, chúng tôi gồm:Hôm nay, ngày 21/09/2026, tại Hà Nội, chúng tôi gồm:
BÊN A: {{ben_a.ten}}BÊN A: Công ty TNHH Thương mại và Dịch vụ An Phát
Đại diện: {{ben_a.dai_dien}}, chức vụ {{ben_a.chuc_vu}}Đại diện: Ông Nguyễn Văn Hùng, chức vụ Giám đốc
BÊN B: {{ben_b.ten}}BÊN B: Công ty Cổ phần O3O
{{#hang}}{{@index}} | {{ten}} | {{so_luong}} | {{thanh_tien}}{{/hang}}1 | O3O Office Online, bản doanh nghiệp, 120 kết nối, thuê bao 1 năm | 1 | 600 USD
Tổng giá trị: {{tong_tien}} ({{tong_tien_bang_chu}}).Tổng giá trị: 600 USD (Sáu trăm đô la Mỹ).
Thời hạn: {{thoi_han}}. Hỗ trợ triển khai: {{ho_tro_trien_khai}}.Thời hạn: 12 tháng kể từ ngày ký. Hỗ trợ triển khai: Có.
JSONDữ liệu
{
  "so_hop_dong": "HD-2026-091",
  "ngay_ky": "21/09/2026",
  "dia_diem": "Hà Nội",
  "ben_a": {
    "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
    "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội",
    "dai_dien": "Ông Nguyễn Văn Hùng",
    "chuc_vu": "Giám đốc"
  },
  "ben_b": {
    "ten": "Công ty Cổ phần O3O",
    "dai_dien": "Bà Trần Thu Hà",
    "chuc_vu": "Giám đốc kinh doanh"
  },
  "hang": [
    {
      "ten": "O3O Office Online, bản doanh nghiệp, 120 kết nối, thuê bao 1 năm",
      "so_luong": 1,
      "thanh_tien": "600 USD"
    }
  ],
  "tong_tien": "600 USD",
  "tong_tien_bang_chu": "Sáu trăm đô la Mỹ",
  "thoi_han": "12 tháng kể từ ngày ký",
  "ho_tro_trien_khai": true
}
Điền mẫu hợp đồng, nhận PDF
curl -sS http://localhost:8080/v1/template/render \
  -H "Authorization: Bearer O3O_DEMO_KEY" \
  -F "template=@hop-dong-mau.docx" \
  -F "data=<-" \
  -F "to=pdf" \
  -o hop-dong.pdf -w "HTTP %{http_code}\n" <<'JSON'
{
  "so_hop_dong": "HD-2026-091",
  "ngay_ky": "21/09/2026",
  "dia_diem": "Hà Nội",
  "ben_a": {
    "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
    "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội",
    "dai_dien": "Ông Nguyễn Văn Hùng",
    "chuc_vu": "Giám đốc"
  },
  "ben_b": {
    "ten": "Công ty Cổ phần O3O",
    "dai_dien": "Bà Trần Thu Hà",
    "chuc_vu": "Giám đốc kinh doanh"
  },
  "hang": [
    {
      "ten": "O3O Office Online, bản doanh nghiệp, 120 kết nối, thuê bao 1 năm",
      "so_luong": 1,
      "thanh_tien": "600 USD"
    }
  ],
  "tong_tien": "600 USD",
  "tong_tien_bang_chu": "Sáu trăm đô la Mỹ",
  "thoi_han": "12 tháng kể từ ngày ký",
  "ho_tro_trien_khai": true
}
JSON
import { readFile, writeFile } from "node:fs/promises";

const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };

const form = new FormData();
form.append("template", new Blob([await readFile("hop-dong-mau.docx")]), "hop-dong-mau.docx");
form.append("data", JSON.stringify({
  "so_hop_dong": "HD-2026-091",
  "ngay_ky": "21/09/2026",
  "dia_diem": "Hà Nội",
  "ben_a": {
    "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
    "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội",
    "dai_dien": "Ông Nguyễn Văn Hùng",
    "chuc_vu": "Giám đốc"
  },
  "ben_b": {
    "ten": "Công ty Cổ phần O3O",
    "dai_dien": "Bà Trần Thu Hà",
    "chuc_vu": "Giám đốc kinh doanh"
  },
  "hang": [
    {
      "ten": "O3O Office Online, bản doanh nghiệp, 120 kết nối, thuê bao 1 năm",
      "so_luong": 1,
      "thanh_tien": "600 USD"
    }
  ],
  "tong_tien": "600 USD",
  "tong_tien_bang_chu": "Sáu trăm đô la Mỹ",
  "thoi_han": "12 tháng kể từ ngày ký",
  "ho_tro_trien_khai": true
}));
form.append("to", "pdf");

const res = await fetch(`${BASE_URL}/v1/template/render`, {
  method: "POST",
  headers: HEADERS,
  body: form,
  signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("hop-dong.pdf", Buffer.from(await res.arrayBuffer()));
import json

import requests

BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}

with open("hop-dong-mau.docx", "rb") as f:
    r = requests.post(
        f"{BASE_URL}/v1/template/render",
        headers=HEADERS,
        files={"template": ("hop-dong-mau.docx", f)},
        data={
            "data": json.dumps({
                "so_hop_dong": "HD-2026-091",
                "ngay_ky": "21/09/2026",
                "dia_diem": "Hà Nội",
                "ben_a": {
                    "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
                    "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội",
                    "dai_dien": "Ông Nguyễn Văn Hùng",
                    "chuc_vu": "Giám đốc"
                },
                "ben_b": {
                    "ten": "Công ty Cổ phần O3O",
                    "dai_dien": "Bà Trần Thu Hà",
                    "chuc_vu": "Giám đốc kinh doanh"
                },
                "hang": [
                    {
                        "ten": "O3O Office Online, bản doanh nghiệp, 120 kết nối, thuê bao 1 năm",
                        "so_luong": 1,
                        "thanh_tien": "600 USD"
                    }
                ],
                "tong_tien": "600 USD",
                "tong_tien_bang_chu": "Sáu trăm đô la Mỹ",
                "thoi_han": "12 tháng kể từ ngày ký",
                "ho_tro_trien_khai": True
            }, ensure_ascii=False),
            "to": "pdf",
        },
        timeout=90,
    )
if not r.ok:
    raise RuntimeError(f"{r.status_code}: {r.text}")
with open("hop-dong.pdf", "wb") as fh:
    fh.write(r.content)
<?php
$ch = curl_init("http://localhost:8080/v1/template/render");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY"],
    CURLOPT_POSTFIELDS => [
        'template' => new CURLFile('hop-dong-mau.docx'),
        'data' => json_encode([
            'so_hop_dong' => 'HD-2026-091',
            'ngay_ky' => '21/09/2026',
            'dia_diem' => 'Hà Nội',
            'ben_a' => [
                'ten' => 'Công ty TNHH Thương mại và Dịch vụ An Phát',
                'dia_chi' => 'Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội',
                'dai_dien' => 'Ông Nguyễn Văn Hùng',
                'chuc_vu' => 'Giám đốc',
            ],
            'ben_b' => [
                'ten' => 'Công ty Cổ phần O3O',
                'dai_dien' => 'Bà Trần Thu Hà',
                'chuc_vu' => 'Giám đốc kinh doanh',
            ],
            'hang' => [
                [
                    'ten' => 'O3O Office Online, bản doanh nghiệp, 120 kết nối, thuê bao 1 năm',
                    'so_luong' => 1,
                    'thanh_tien' => '600 USD',
                ],
            ],
            'tong_tien' => '600 USD',
            'tong_tien_bang_chu' => 'Sáu trăm đô la Mỹ',
            'thoi_han' => '12 tháng kể từ ngày ký',
            'ho_tro_trien_khai' => true,
        ], JSON_UNESCAPED_UNICODE),
        'to' => 'pdf',
    ],
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 90,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false || $status >= 400) {
    throw new RuntimeException("HTTP $status: " . ($body === false ? curl_error($ch) : $body));
}
file_put_contents("hop-dong.pdf", $body);
using System.Net.Http.Headers;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "O3O_DEMO_KEY");

var data = """
    {
      "so_hop_dong": "HD-2026-091",
      "ngay_ky": "21/09/2026",
      "dia_diem": "Hà Nội",
      "ben_a": {
        "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
        "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội",
        "dai_dien": "Ông Nguyễn Văn Hùng",
        "chuc_vu": "Giám đốc"
      },
      "ben_b": {
        "ten": "Công ty Cổ phần O3O",
        "dai_dien": "Bà Trần Thu Hà",
        "chuc_vu": "Giám đốc kinh doanh"
      },
      "hang": [
        {
          "ten": "O3O Office Online, bản doanh nghiệp, 120 kết nối, thuê bao 1 năm",
          "so_luong": 1,
          "thanh_tien": "600 USD"
        }
      ],
      "tong_tien": "600 USD",
      "tong_tien_bang_chu": "Sáu trăm đô la Mỹ",
      "thoi_han": "12 tháng kể từ ngày ký",
      "ho_tro_trien_khai": true
    }
    """;

using var form = new MultipartFormDataContent();
form.Add(new ByteArrayContent(await File.ReadAllBytesAsync("hop-dong-mau.docx")), "template", "hop-dong-mau.docx");
form.Add(new StringContent(data), "data");
form.Add(new StringContent("pdf"), "to");

using var res = await http.PostAsync("http://localhost:8080/v1/template/render", form);
if (!res.IsSuccessStatusCode)
    throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("hop-dong.pdf", await res.Content.ReadAsByteArrayAsync());
200Chế độ đồng bộ: thân phản hồi là tệp nhị phân. Khối dưới đây liệt kê các header đi kèm (bản cộng đồng, nên có cả header theo ngày).
{
  "X-O3O-Request-Id": "req_0123456789abcdef",
  "X-O3O-Job-Id": "job_5f0c2a9e41b7d3c8a6e1f024",
  "X-O3O-File-Id": "file_9b3e7d21c4a8f0e65d1b2c37",
  "Content-Disposition": "attachment; filename=\"hop-dong.pdf\"",
  "X-RateLimit-Limit": "10",
  "X-RateLimit-Remaining": "9",
  "X-RateLimit-Reset": "1789984860",
  "X-RateLimit-Limit-Day": "200",
  "X-RateLimit-Remaining-Day": "187"
}

Ví dụ: hoá đơn bán hàng#

Mẫu hoa-don-mau.docx có phần đầu Số: {{so}}, Ngày: {{ngay}}, Khách hàng: {{khach.ten}}, Địa chỉ: {{khach.dia_chi}}; một bảng có hàng lặp; và phần cuối Cộng tiền hàng: {{tong_tien}}, Ghi chú: {{ghi_chu}}, Người lập: {{nguoi_lap}}. Bảng trong mẫu:

STTTên hàng hoá, dịch vụĐơn vị tínhSố lượngĐơn giáThành tiền
{{#hang}}{{@index}}{{ten}}{{dvt}}{{so_luong}}{{don_gia}}{{thanh_tien}}{{/hang}}
JSONDữ liệu
{
  "so": "0000412",
  "ngay": "21/09/2026",
  "khach": {
    "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
    "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội"
  },
  "hang": [
    {
      "ten": "Giấy in A4 định lượng 70",
      "dvt": "ram",
      "so_luong": 20,
      "don_gia": "68.000",
      "thanh_tien": "1.360.000"
    },
    {
      "ten": "Mực in laser",
      "dvt": "hộp",
      "so_luong": 2,
      "don_gia": "450.000",
      "thanh_tien": "900.000"
    },
    {
      "ten": "Bìa hồ sơ",
      "dvt": "chiếc",
      "so_luong": 50,
      "don_gia": "3.500",
      "thanh_tien": "175.000"
    }
  ],
  "tong_tien": "2.435.000",
  "ghi_chu": "Thanh toán chuyển khoản trong 7 ngày.\nNội dung chuyển khoản: số hoá đơn.",
  "nguoi_lap": "Lê Minh Châu"
}
Mẫu lấy từ URL, báo lỗi nếu thiếu trường
curl -sS http://localhost:8080/v1/template/render \
  -H "Authorization: Bearer O3O_DEMO_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @- \
  -o hoa-don-0000412.pdf -w "HTTP %{http_code}\n" <<'JSON'
{
  "template_url": "https://example.com/mau/hoa-don-mau.docx",
  "data": {
    "so": "0000412",
    "ngay": "21/09/2026",
    "khach": {
      "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
      "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội"
    },
    "hang": [
      {
        "ten": "Giấy in A4 định lượng 70",
        "dvt": "ram",
        "so_luong": 20,
        "don_gia": "68.000",
        "thanh_tien": "1.360.000"
      },
      {
        "ten": "Mực in laser",
        "dvt": "hộp",
        "so_luong": 2,
        "don_gia": "450.000",
        "thanh_tien": "900.000"
      },
      {
        "ten": "Bìa hồ sơ",
        "dvt": "chiếc",
        "so_luong": 50,
        "don_gia": "3.500",
        "thanh_tien": "175.000"
      }
    ],
    "tong_tien": "2.435.000",
    "ghi_chu": "Thanh toán chuyển khoản trong 7 ngày.\nNội dung chuyển khoản: số hoá đơn.",
    "nguoi_lap": "Lê Minh Châu"
  },
  "to": "pdf",
  "filename": "hoa-don-0000412.pdf",
  "options": {
    "missing": "error"
  }
}
JSON
import { writeFile } from "node:fs/promises";

const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };

const payload = {
  "template_url": "https://example.com/mau/hoa-don-mau.docx",
  "data": {
    "so": "0000412",
    "ngay": "21/09/2026",
    "khach": {
      "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
      "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội"
    },
    "hang": [
      {
        "ten": "Giấy in A4 định lượng 70",
        "dvt": "ram",
        "so_luong": 20,
        "don_gia": "68.000",
        "thanh_tien": "1.360.000"
      },
      {
        "ten": "Mực in laser",
        "dvt": "hộp",
        "so_luong": 2,
        "don_gia": "450.000",
        "thanh_tien": "900.000"
      },
      {
        "ten": "Bìa hồ sơ",
        "dvt": "chiếc",
        "so_luong": 50,
        "don_gia": "3.500",
        "thanh_tien": "175.000"
      }
    ],
    "tong_tien": "2.435.000",
    "ghi_chu": "Thanh toán chuyển khoản trong 7 ngày.\nNội dung chuyển khoản: số hoá đơn.",
    "nguoi_lap": "Lê Minh Châu"
  },
  "to": "pdf",
  "filename": "hoa-don-0000412.pdf",
  "options": {
    "missing": "error"
  }
};

const res = await fetch(`${BASE_URL}/v1/template/render`, {
  method: "POST",
  headers: { ...HEADERS, "Content-Type": "application/json" },
  body: JSON.stringify(payload),
  signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("hoa-don-0000412.pdf", Buffer.from(await res.arrayBuffer()));
import requests

BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}

payload = {
    "template_url": "https://example.com/mau/hoa-don-mau.docx",
    "data": {
        "so": "0000412",
        "ngay": "21/09/2026",
        "khach": {
            "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
            "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội"
        },
        "hang": [
            {
                "ten": "Giấy in A4 định lượng 70",
                "dvt": "ram",
                "so_luong": 20,
                "don_gia": "68.000",
                "thanh_tien": "1.360.000"
            },
            {
                "ten": "Mực in laser",
                "dvt": "hộp",
                "so_luong": 2,
                "don_gia": "450.000",
                "thanh_tien": "900.000"
            },
            {
                "ten": "Bìa hồ sơ",
                "dvt": "chiếc",
                "so_luong": 50,
                "don_gia": "3.500",
                "thanh_tien": "175.000"
            }
        ],
        "tong_tien": "2.435.000",
        "ghi_chu": "Thanh toán chuyển khoản trong 7 ngày.\nNội dung chuyển khoản: số hoá đơn.",
        "nguoi_lap": "Lê Minh Châu"
    },
    "to": "pdf",
    "filename": "hoa-don-0000412.pdf",
    "options": {
        "missing": "error"
    }
}
r = requests.post(f"{BASE_URL}/v1/template/render", headers=HEADERS, json=payload, timeout=90)
if not r.ok:
    raise RuntimeError(f"{r.status_code}: {r.text}")
with open("hoa-don-0000412.pdf", "wb") as fh:
    fh.write(r.content)
<?php
$payload = json_encode([
    'template_url' => 'https://example.com/mau/hoa-don-mau.docx',
    'data' => [
        'so' => '0000412',
        'ngay' => '21/09/2026',
        'khach' => [
            'ten' => 'Công ty TNHH Thương mại và Dịch vụ An Phát',
            'dia_chi' => 'Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội',
        ],
        'hang' => [
            [
                'ten' => 'Giấy in A4 định lượng 70',
                'dvt' => 'ram',
                'so_luong' => 20,
                'don_gia' => '68.000',
                'thanh_tien' => '1.360.000',
            ],
            [
                'ten' => 'Mực in laser',
                'dvt' => 'hộp',
                'so_luong' => 2,
                'don_gia' => '450.000',
                'thanh_tien' => '900.000',
            ],
            [
                'ten' => 'Bìa hồ sơ',
                'dvt' => 'chiếc',
                'so_luong' => 50,
                'don_gia' => '3.500',
                'thanh_tien' => '175.000',
            ],
        ],
        'tong_tien' => '2.435.000',
        'ghi_chu' => 'Thanh toán chuyển khoản trong 7 ngày.
Nội dung chuyển khoản: số hoá đơn.',
        'nguoi_lap' => 'Lê Minh Châu',
    ],
    'to' => 'pdf',
    'filename' => 'hoa-don-0000412.pdf',
    'options' => ['missing' => 'error'],
], JSON_UNESCAPED_UNICODE);

$ch = curl_init("http://localhost:8080/v1/template/render");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY", "Content-Type: application/json"],
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 90,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($body === false || $status >= 400) {
    throw new RuntimeException("HTTP $status: " . ($body === false ? curl_error($ch) : $body));
}
file_put_contents("hoa-don-0000412.pdf", $body);
using System.Net.Http.Headers;
using System.Text;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "O3O_DEMO_KEY");

var json = """
    {
      "template_url": "https://example.com/mau/hoa-don-mau.docx",
      "data": {
        "so": "0000412",
        "ngay": "21/09/2026",
        "khach": {
          "ten": "Công ty TNHH Thương mại và Dịch vụ An Phát",
          "dia_chi": "Số 18 đường Trần Hưng Đạo, quận Hoàn Kiếm, Hà Nội"
        },
        "hang": [
          {
            "ten": "Giấy in A4 định lượng 70",
            "dvt": "ram",
            "so_luong": 20,
            "don_gia": "68.000",
            "thanh_tien": "1.360.000"
          },
          {
            "ten": "Mực in laser",
            "dvt": "hộp",
            "so_luong": 2,
            "don_gia": "450.000",
            "thanh_tien": "900.000"
          },
          {
            "ten": "Bìa hồ sơ",
            "dvt": "chiếc",
            "so_luong": 50,
            "don_gia": "3.500",
            "thanh_tien": "175.000"
          }
        ],
        "tong_tien": "2.435.000",
        "ghi_chu": "Thanh toán chuyển khoản trong 7 ngày.\nNội dung chuyển khoản: số hoá đơn.",
        "nguoi_lap": "Lê Minh Châu"
      },
      "to": "pdf",
      "filename": "hoa-don-0000412.pdf",
      "options": {
        "missing": "error"
      }
    }
    """;
using var res = await http.PostAsync("http://localhost:8080/v1/template/render",
    new StringContent(json, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
    throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("hoa-don-0000412.pdf", await res.Content.ReadAsByteArrayAsync());

Bảng sau khi điền có ba hàng hàng hoá; {{ghi_chu}} in thành hai dòng nhờ ký tự \n:

STTTên hàng hoá, dịch vụĐơn vị tínhSố lượngĐơn giáThành tiền
1Giấy in A4 định lượng 70ram2068.0001.360.000
2Mực in laserhộp2450.000900.000
3Bìa hồ sơchiếc503.500175.000

Mã lỗi thường gặp#

HTTPKhi nào
400bad_requestThiếu template hoặc template_url, data không phải đối tượng, JSON hỏng.
401unauthorizedThiếu hoặc sai khoá. detail.reasonmissing, invalid hoặc no_keys_configured.
403forbidden_featureGọi endpoint này ở bản cộng đồng.
413file_too_largeTệp vào hoặc tệp ra vượt max_file_mb. detail.limit_mb.
415unsupported_formatMẫu không phải docx hoặc odt.
422template_errorThẻ lặp không cân, trường thiếu khi missing = "error", hoặc mảng, đối tượng ở vị trí trường đơn. detail.fields.
422macro_not_allowedMẫu có macro (phần vbaProject.bin, kiểu nội dung macroEnabled, thư mục Basic/ hay Scripts/).
422corrupt_sourceLibreOffice không mở được tệp nguồn.
422url_not_allowedURL vi phạm quy tắc chống SSRF.
422download_failedKhông tải được URL nguồn.
429rate_limitedVượt hạn mức. detail.windowminute hoặc day; kèm Retry-After.
504timeoutChế độ đồng bộ quá sync_timeout_seconds, hoặc job quá job_timeout_seconds. Worker bị dừng và dựng lại.
403Gọi ở bản cộng đồng.
{
  "error": {
    "code": "forbidden_feature",
    "message": "Điền mẫu chỉ có ở bản doanh nghiệp.",
    "detail": {
      "feature": "POST /v1/template/render",
      "edition": "community"
    },
    "request_id": "req_0123456789abcdef"
  }
}
422Thiếu trường khi options.missing = "error".
{
  "error": {
    "code": "template_error",
    "message": "Mẫu có trường không có trong dữ liệu.",
    "detail": {
      "fields": [
        "ben_a.dai_dien",
        "tong_tien"
      ]
    },
    "request_id": "req_0123456789abcdef"
  }
}
422Mẫu có macro.
{
  "error": {
    "code": "macro_not_allowed",
    "message": "Mẫu có chứa macro. DocBuilder không nhận mẫu có macro: hãy lưu lại thành docx hoặc odt không có macro rồi gửi lại.",
    "detail": {
      "reason": "vba_project",
      "part": "word/vbaProject.bin"
    },
    "request_id": "req_0123456789abcdef"
  }
}

Sắp có#

Sắp có

Điều kiện và ảnh trong mẫu, bộ lọc ngày và tiền, điền mẫu hàng loạt trong một lần gọi (/v1/template/render-batch) và soi danh sách trường của mẫu (/v1/template/inspect).