O3O DocBuilder and conversion
Template filling
POST /v1/template/render fills JSON data into a docx or odt template written by your users: {{name}} fields, nested paths, table row loops; returns docx, odt or PDF. Enterprise only.
On this page
Business staff write the template in a familiar editor and place {{field_name}} tags where data goes; your system only sends JSON data. Changing the wording or layout of a template needs no code change.
/v1/template/renderFill JSON data into a docx or odt template.
Writing a template#
A template is an ordinary docx or odt file. Places to fill are typed as plain text inside double curly braces. Replacement covers the body, tables, headers and footers; the text formatting at the tag position (font, size, bold, colour) is kept.
| Syntax | Meaning |
|---|---|
{{ten}} | Replaced by data.ten. Field names match [A-Za-z_][A-Za-z0-9_]*; spaces next to the braces are allowed, e.g. {{ ten }}. |
{{khach.ten}} | Follows a path into nested objects. |
{{#hang}} … {{/hang}} | Repeats a table row for the array data.hang, see below. |
{{ten}} trong hàng lặp | Looked up in the current element first, then at the root of data. |
{{@index}} | Position inside a row loop, starting at 1. |
How values are printed#
| Value in data | Printed as |
|---|---|
| String | As is. A \n becomes a line break inside the paragraph. |
| Integer | No decimals: 20. |
| Decimal number | Up to 6 significant digits. |
true, false | Có, Không (Vietnamese for yes and no). |
null | Empty string. |
| Array or object in a plain field | Error 422 template_error. |
Table row loops#
Put {{#hang}} in the FIRST cell and {{/hang}} in the LAST cell of the SAME table row. That row is copied once per element of the data.hang array and both tags are removed; an empty array removes the template row. The header row and the other rows of the table stay as they are.
| STT | Item | Quantity | Amount |
|---|---|---|---|
{{#hang}}{{@index}} | {{ten}} | {{so_luong}} | {{thanh_tien}}{{/hang}} |
Missing fields#
options.missing | Field absent from data |
|---|---|
empty | Default. Replaced with an empty string. |
keep | The tag stays in the document, handy when proofing a template. |
error | Returns 422 template_error with the missing fields in detail.fields. |
Templates with macros are rejected#
Template filling reads and writes the template's XML parts directly and does not load the template through LibreOffice; the other parts of the file are copied unchanged into the result, so a template with macros would carry those macros into the result. DocBuilder therefore rejects any template that contains macros, including a docm file renamed to docx, and returns 422 macro_not_allowed with a Vietnamese message. A template counts as containing macros when:
- An OOXML (docx) file has a
vbaProject.binpart, declares amacroEnabledcontent type in[Content_Types].xml, or has a relationship pointing at a VBA part (even a renamed one). - An ODF (odt) file has a
Basic/orScripts/folder.
The template is checked on receipt, before any job is created, so the error comes back immediately even with async = true. detail.reason gives the reason (vba_project, macro_enabled_content_type, odf_basic, odf_scripts); detail.part is the name of the offending part in the file.
Parameters#
multipart/form-data body
templatefilerequiredThe docx or odt template.datastring (JSON)requiredThe data as a JSON STRING; the root must be an object.todocx | odt | pdfoptionalDefault:the template's formatTarget format.filenamestring, ≤ 255optionalResult file name.optionsstring (JSON)optionalFor example{"missing": "error"}.asyncbooleanoptionalDefault:falsetrue: return202with a job right away.callback_urlstring (URL)optionalNeeds thecallbackfeature in the token.
application/json body
template_urlURL, ≤ 2048requiredURL of the template, subject to the SSRF rules.dataobjectrequiredThe data to fill in.todocx | odt | pdfoptionalDefault:the template's formatTarget format.filenamestring, ≤ 255optionalResult file name.optionsobjectoptionalTemplate options.asyncbooleanoptionalDefault:falsetrue: return202with a job right away.callback_urlstring (URL)optionalNeeds thecallbackfeature in the token.
The options object
missingempty | keep | erroroptionalDefault:emptyHow missing fields are handled.
Example: service contract#
The template hop-dong-mau.docx contains the paragraphs below (left: text typed in the template; right: the result after filling in the data further down).
| Typed in the template | Result |
|---|---|
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ó. |
{
"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
}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
}
JSONimport { 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());{
"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"
}Example: sales invoice#
The template hoa-don-mau.docx has a top part Số: {{so}}, Ngày: {{ngay}}, Khách hàng: {{khach.ten}}, Địa chỉ: {{khach.dia_chi}}; a table with a loop row; and a bottom part Cộng tiền hàng: {{tong_tien}}, Ghi chú: {{ghi_chu}}, Người lập: {{nguoi_lap}}. The table in the template:
| STT | Item | Unit | Quantity | Unit price | Amount |
|---|---|---|---|---|---|
{{#hang}}{{@index}} | {{ten}} | {{dvt}} | {{so_luong}} | {{don_gia}} | {{thanh_tien}}{{/hang}} |
{
"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"
}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"
}
}
JSONimport { 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());After filling, the table has three item rows; {{ghi_chu}} prints on two lines thanks to \n:
| STT | Item | Unit | Quantity | Unit price | Amount |
|---|---|---|---|---|---|
| 1 | Giấy in A4 định lượng 70 | ram | 20 | 68.000 | 1.360.000 |
| 2 | Mực in laser | hộp | 2 | 450.000 | 900.000 |
| 3 | Bìa hồ sơ | chiếc | 50 | 3.500 | 175.000 |
Common errors#
| HTTP | Code | When |
|---|---|---|
| 400 | bad_request | Missing template or template_url, data not an object, malformed JSON. |
| 401 | unauthorized | Missing or wrong key. detail.reason is missing, invalid or no_keys_configured. |
| 403 | forbidden_feature | Calling this endpoint on the Community plan. |
| 413 | file_too_large | Input or output file exceeds max_file_mb. detail.limit_mb. |
| 415 | unsupported_format | The template is not docx or odt. |
| 422 | template_error | Unbalanced loop tags, missing fields with missing = "error", or an array or object in a plain field. detail.fields. |
| 422 | macro_not_allowed | The template contains macros (a vbaProject.bin part, a macroEnabled content type, or a Basic/ or Scripts/ folder). |
| 422 | corrupt_source | LibreOffice cannot open the source file. |
| 422 | url_not_allowed | The URL violates the SSRF protection rules. |
| 422 | download_failed | The source URL could not be downloaded. |
| 429 | rate_limited | Rate limit exceeded. detail.window is minute or day; comes with Retry-After. |
| 504 | timeout | Synchronous mode exceeded sync_timeout_seconds, or the job exceeded job_timeout_seconds. The worker is killed and restarted. |
{
"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"
}
}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"
}
}{
"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"
}
}Coming later#
Conditions and images in templates, date and money filters, batch filling in one call (/v1/template/render-batch) and listing a template's fields (/v1/template/inspect).