O3O DocBuilder và chuyển đổi
Chuyển đổi định dạng
POST /v1/convert đổi một tệp sang định dạng khác trong cùng họ tài liệu (văn bản, bảng tính, trình chiếu), kể cả PDF, PDF/A và ảnh PNG hoặc JPG của một trang; chạy đồng bộ hoặc bất đồng bộ, nguồn tải lên hoặc lấy từ URL.
Trong trang này
Gửi một tệp nguồn, nhận lại tệp đích. Nguồn gửi theo một trong hai cách: tải tệp lên bằng multipart/form-data (trường file), hoặc gửi application/json có url để DocBuilder tự tải về. Chỉ chuyển trong cùng một họ tài liệu; khác họ, ví dụ docx sang xlsx, trả 415 unsupported_format.
/v1/convertChuyển đổi định dạng. Đồng bộ trả thẳng tệp; bất đồng bộ trả job.
Tham số#
Thân multipart/form-data
filefilebắt buộcTệp nguồn.tostringbắt buộcĐịnh dạng đích: docx, odt, rtf, txt, html, pdf, xlsx, ods, csv, pptx, odp, png, jpg.fromstringtuỳ chọnĐịnh dạng nguồn: docx, doc, odt, rtf, txt, html, xlsx, xls, ods, csv, pptx, ppt, odp. Không có thì lấy theo đuôi tên tệp.filenamestringtuỳ chọnMặc định:tên nguồn đổi đuôiTên tệp kết quả, tối đa 255 ký tự.optionsstring (JSON)tuỳ chọnTuỳ chọn chuyển đổi, gửi dạng CHUỖI JSON (xem bảng dưới).asyncbooleantuỳ chọnMặc định:falsetrue: trả ngay202kèm job thay vì chờ tệp.callback_urlstring (URL)tuỳ chọnChỉ bản doanh nghiệp có tính năngcallback. NhậnPOSTkhi job xong hoặc lỗi.
Thân application/json
urlstring (URL)bắt buộcURL tải tệp nguồn, tối đa 2048 ký tự, chịu quy tắc chống SSRF.tostringbắt buộcĐịnh dạng đích: docx, odt, rtf, txt, html, pdf, xlsx, ods, csv, pptx, odp, png, jpg.fromstringtuỳ chọnĐịnh dạng nguồn: docx, doc, odt, rtf, txt, html, xlsx, xls, ods, csv, pptx, ppt, odp. Không có thì lấy theo đuôi tên tệp.filenamestringtuỳ chọnMặc định:tên nguồn đổi đuôiTên tệp kết quả, tối đa 255 ký tự.optionsobjecttuỳ chọnTuỳ chọn chuyển đổi (xem bảng dưới).asyncbooleantuỳ chọnMặc định:falsetrue: trả ngay202kèm job thay vì chờ tệp.callback_urlstring (URL)tuỳ chọnChỉ bản doanh nghiệp có tính năngcallback. NhậnPOSTkhi job xong hoặc lỗi.
Đối tượng options
passwordstringtuỳ chọnMật khẩu MỞ tệp nguồn. Thiếu hoặc sai:422 password_required.pdf.pdfabooleantuỳ chọnMặc định:falseXuất PDF/A-2b để lưu trữ lâu dài.pdf.page_rangestringtuỳ chọnMặc định:mọi trangKhoảng trang, ví dụ1-3,5. Mẫu:^[0-9]+(-[0-9]+)?(,[0-9]+(-[0-9]+)?)*$.pdf.image_qualityinteger 1–100tuỳ chọnMặc định:90Chất lượng ảnh bên trong PDF.image.pageinteger ≥ 1tuỳ chọnMặc định:1Trang được xuất khi đích làpnghoặcjpg.image.widthinteger 16–4000tuỳ chọnMặc định:1240Chiều rộng ảnh, điểm ảnh; chiều cao theo tỷ lệ trang.csv.delimiterstring, 1 ký tựtuỳ chọnMặc định:,Ký tự phân tách cột.csv.encodingutf-8 | windows-1258 | windows-1252tuỳ chọnMặc định:utf-8Bảng mã của tệp CSV.csv.sheetinteger ≥ 1tuỳ chọnMặc định:1Trang tính được xuất khi đích làcsv.
Chuyển đổi đồng bộ#
Mặc định (async = false) DocBuilder giữ kết nối tới khi xong và trả thẳng tệp kết quả với mã 200. Quá sync_timeout_seconds (60 giây ở cả hai gói) thì trả 504 timeout; tệp lớn hoặc nhiều trang nên dùng chế độ bất đồng bộ. Mã tệp trong header X-O3O-File-Id tải lại được ở GET /v1/files/{id} tới khi hết thời gian giữ.
curl -sS http://localhost:8080/v1/convert \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-F "file=@bao-cao.docx" \
-F "to=pdf" \
-o bao-cao.pdf -w "HTTP %{http_code}\n"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("file", new Blob([await readFile("bao-cao.docx")]), "bao-cao.docx");
form.append("to", "pdf");
const res = await fetch(`${BASE_URL}/v1/convert`, {
method: "POST",
headers: HEADERS,
body: form,
signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("bao-cao.pdf", Buffer.from(await res.arrayBuffer()));import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("bao-cao.docx", "rb") as f:
r = requests.post(
f"{BASE_URL}/v1/convert",
headers=HEADERS,
files={"file": ("bao-cao.docx", f)},
data={
"to": "pdf",
},
timeout=90,
)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("bao-cao.pdf", "wb") as fh:
fh.write(r.content)<?php
$ch = curl_init("http://localhost:8080/v1/convert");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY"],
CURLOPT_POSTFIELDS => [
'file' => new CURLFile('bao-cao.docx'),
'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("bao-cao.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");
using var form = new MultipartFormDataContent();
form.Add(new ByteArrayContent(await File.ReadAllBytesAsync("bao-cao.docx")), "file", "bao-cao.docx");
form.Add(new StringContent("pdf"), "to");
using var res = await http.PostAsync("http://localhost:8080/v1/convert", form);
if (!res.IsSuccessStatusCode)
throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("bao-cao.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=\"bao-cao.pdf\"",
"X-RateLimit-Limit": "10",
"X-RateLimit-Remaining": "9",
"X-RateLimit-Reset": "1789984860",
"X-RateLimit-Limit-Day": "200",
"X-RateLimit-Remaining-Day": "187"
}Nguồn từ URL#
Gửi application/json với url. DocBuilder tự tải tệp theo quy tắc chống SSRF: chỉ http và https, không có phần user:pass@, tối đa 2048 ký tự; mọi địa chỉ mà tên miền phân giải ra phải là địa chỉ công cộng, và DocBuilder nối thẳng tới đúng địa chỉ IP đã kiểm, không phân giải lại (chống DNS rebinding); tự xử lý tối đa 3 lần chuyển hướng và kiểm lại từng chặng; chỉ mã 200 là thành công; thời gian tải tối đa theo O3O_FETCH_TIMEOUT_SECONDS (mặc định 30 giây).
curl -sS http://localhost:8080/v1/convert \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/files/bao-cao.docx", "to": "pdf", "options": {"pdf": {"pdfa": true}}}' \
-o bao-cao.pdf -w "HTTP %{http_code}\n"import { writeFile } from "node:fs/promises";
const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };
const payload = {
"url": "https://example.com/files/bao-cao.docx",
"to": "pdf",
"options": {
"pdf": {
"pdfa": true
}
}
};
const res = await fetch(`${BASE_URL}/v1/convert`, {
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("bao-cao.pdf", Buffer.from(await res.arrayBuffer()));import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
payload = {
"url": "https://example.com/files/bao-cao.docx",
"to": "pdf",
"options": {
"pdf": {
"pdfa": True
}
}
}
r = requests.post(f"{BASE_URL}/v1/convert", headers=HEADERS, json=payload, timeout=90)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("bao-cao.pdf", "wb") as fh:
fh.write(r.content)<?php
$payload = json_encode([
'url' => 'https://example.com/files/bao-cao.docx',
'to' => 'pdf',
'options' => ['pdf' => ['pdfa' => true]],
], JSON_UNESCAPED_UNICODE);
$ch = curl_init("http://localhost:8080/v1/convert");
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("bao-cao.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 = """
{
"url": "https://example.com/files/bao-cao.docx",
"to": "pdf",
"options": {
"pdf": {
"pdfa": true
}
}
}
""";
using var res = await http.PostAsync("http://localhost:8080/v1/convert",
new StringContent(json, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("bao-cao.pdf", await res.Content.ReadAsByteArrayAsync());Chuyển đổi bất đồng bộ#
Đặt async = true để nhận ngay 202 kèm đối tượng job và header Location: /v1/jobs/{id}. Sau đó hỏi trạng thái và tải tệp như ở trang Việc bất đồng bộ. Cả hai gói đều dùng được chế độ này; riêng callback_url chỉ có ở bản doanh nghiệp có tính năng callback, bản cộng đồng gửi tham số này nhận 403 forbidden_feature.
curl -sS http://localhost:8080/v1/convert \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-F "file=@bao-cao.docx" \
-F "to=pdf" \
-F "async=true"import { readFile } from "node:fs/promises";
const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };
const form = new FormData();
form.append("file", new Blob([await readFile("bao-cao.docx")]), "bao-cao.docx");
form.append("to", "pdf");
form.append("async", "true");
const res = await fetch(`${BASE_URL}/v1/convert`, {
method: "POST",
headers: HEADERS,
body: form,
signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
console.log(await res.json());import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("bao-cao.docx", "rb") as f:
r = requests.post(
f"{BASE_URL}/v1/convert",
headers=HEADERS,
files={"file": ("bao-cao.docx", f)},
data={
"to": "pdf",
"async": "true",
},
timeout=90,
)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
print(r.json())<?php
$ch = curl_init("http://localhost:8080/v1/convert");
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY"],
CURLOPT_POSTFIELDS => [
'file' => new CURLFile('bao-cao.docx'),
'to' => 'pdf',
'async' => 'true',
],
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));
}
print_r(json_decode($body, true));using System.Net.Http.Headers;
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(90) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "O3O_DEMO_KEY");
using var form = new MultipartFormDataContent();
form.Add(new ByteArrayContent(await File.ReadAllBytesAsync("bao-cao.docx")), "file", "bao-cao.docx");
form.Add(new StringContent("pdf"), "to");
form.Add(new StringContent("true"), "async");
using var res = await http.PostAsync("http://localhost:8080/v1/convert", form);
if (!res.IsSuccessStatusCode)
throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
Console.WriteLine(await res.Content.ReadAsStringAsync());Location trỏ tới /v1/jobs/{id}.{
"id": "job_5f0c2a9e41b7d3c8a6e1f024",
"kind": "convert",
"status": "queued",
"created_at": "2026-09-21T10:00:00Z",
"started_at": null,
"finished_at": null,
"duration_ms": null,
"expires_at": null,
"outputs": [],
"error": null,
"callback": null
}Tuỳ chọn PDF#
pdf.pdfa = truexuất PDF/A-2b, phù hợp lưu trữ hồ sơ dài hạn.pdf.page_rangechọn trang, ví dụ1-3,5là trang 1 tới 3 và trang 5.pdf.image_qualitytừ 1 tới 100 (mặc định 90): số nhỏ cho tệp nhẹ hơn, ảnh kém nét hơn.
curl -sS http://localhost:8080/v1/convert \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-F "file=@bao-cao.docx" \
-F "to=pdf" \
--form-string 'options={"pdf":{"pdfa":true,"page_range":"1-3,5","image_quality":80}}' \
-o bao-cao-trich.pdf -w "HTTP %{http_code}\n"import json
import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("bao-cao.docx", "rb") as f:
r = requests.post(
f"{BASE_URL}/v1/convert",
headers=HEADERS,
files={"file": ("bao-cao.docx", f)},
data={
"to": "pdf",
"options": json.dumps({"pdf": {"pdfa": True, "page_range": "1-3,5", "image_quality": 80}}, ensure_ascii=False),
},
timeout=90,
)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("bao-cao-trich.pdf", "wb") as fh:
fh.write(r.content)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("file", new Blob([await readFile("bao-cao.docx")]), "bao-cao.docx");
form.append("to", "pdf");
form.append("options", JSON.stringify({"pdf": {"pdfa": true, "page_range": "1-3,5", "image_quality": 80}}));
const res = await fetch(`${BASE_URL}/v1/convert`, {
method: "POST",
headers: HEADERS,
body: form,
signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("bao-cao-trich.pdf", Buffer.from(await res.arrayBuffer()));PDF có mật khẩu (đặt mật khẩu cho tệp PDF xuất ra). Hiện options.password chỉ dùng để mở tệp nguồn có mật khẩu.
Xuất ảnh một trang#
Đích png hoặc jpg xuất đúng một trang mỗi lần gọi, chọn bằng options.image.page (mặc định trang 1), rộng options.image.width điểm ảnh (16 tới 4000, mặc định 1240), chiều cao theo tỷ lệ trang. Ảnh đi qua PDF trung gian rồi dựng bằng pdftoppm, nên chọn được trang bất kỳ ở cả văn bản, bảng tính và trình chiếu. Chỉ cần ảnh thu nhỏ thì dùng POST /v1/extract/thumbnail.
curl -sS http://localhost:8080/v1/convert \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-F "file=@gioi-thieu.pptx" \
-F "to=png" \
--form-string 'options={"image":{"page":2,"width":1600}}' \
-o trang-2.png -w "HTTP %{http_code}\n"import json
import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("gioi-thieu.pptx", "rb") as f:
r = requests.post(
f"{BASE_URL}/v1/convert",
headers=HEADERS,
files={"file": ("gioi-thieu.pptx", f)},
data={
"to": "png",
"options": json.dumps({"image": {"page": 2, "width": 1600}}, ensure_ascii=False),
},
timeout=90,
)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("trang-2.png", "wb") as fh:
fh.write(r.content)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("file", new Blob([await readFile("gioi-thieu.pptx")]), "gioi-thieu.pptx");
form.append("to", "png");
form.append("options", JSON.stringify({"image": {"page": 2, "width": 1600}}));
const res = await fetch(`${BASE_URL}/v1/convert`, {
method: "POST",
headers: HEADERS,
body: form,
signal: AbortSignal.timeout(90_000),
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
await writeFile("trang-2.png", Buffer.from(await res.arrayBuffer()));CSV#
Tuỳ chọn options.csv áp khi nguồn hoặc đích là csv: delimiter là một ký tự (mặc định dấu phẩy), encoding là utf-8, windows-1258 hoặc windows-1252, còn sheet chọn trang tính được xuất khi đích là csv (mặc định trang 1). Tệp CSV xuất từ Excel trên máy đặt vùng Việt Nam có thể dùng dấu chấm phẩy và bảng mã windows-1258; khi đó đặt hai tuỳ chọn này cho khớp.
curl -sS http://localhost:8080/v1/convert \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-F "file=@bang-luong.xlsx" \
-F "to=csv" \
--form-string 'options={"csv":{"delimiter":";","sheet":2}}' \
-o theo-phong-ban.csv -w "HTTP %{http_code}\n"import json
import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("bang-luong.xlsx", "rb") as f:
r = requests.post(
f"{BASE_URL}/v1/convert",
headers=HEADERS,
files={"file": ("bang-luong.xlsx", f)},
data={
"to": "csv",
"options": json.dumps({"csv": {"delimiter": ";", "sheet": 2}}, ensure_ascii=False),
},
timeout=90,
)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("theo-phong-ban.csv", "wb") as fh:
fh.write(r.content)curl -sS http://localhost:8080/v1/convert \
-H "Authorization: Bearer O3O_DEMO_KEY" \
-F "file=@du-lieu.csv" \
-F "to=xlsx" \
--form-string 'options={"csv":{"delimiter":";","encoding":"windows-1258"}}' \
-o du-lieu.xlsx -w "HTTP %{http_code}\n"import json
import requests
BASE_URL = "http://localhost:8080"
HEADERS = {"Authorization": "Bearer O3O_DEMO_KEY"}
with open("du-lieu.csv", "rb") as f:
r = requests.post(
f"{BASE_URL}/v1/convert",
headers=HEADERS,
files={"file": ("du-lieu.csv", f)},
data={
"to": "xlsx",
"options": json.dumps({"csv": {"delimiter": ";", "encoding": "windows-1258"}}, ensure_ascii=False),
},
timeout=90,
)
if not r.ok:
raise RuntimeError(f"{r.status_code}: {r.text}")
with open("du-lieu.xlsx", "wb") as fh:
fh.write(r.content)Định dạng hỗ trợ#
| Họ | Nguồn | Đích |
|---|---|---|
text | docx, doc, odt, rtf, txt, html | docx, odt, rtf, txt, html, pdf, png, jpg |
sheet | xlsx, xls, ods, csv | xlsx, ods, csv, html, pdf, png, jpg |
slide | pptx, ppt, odp | pptx, odp, pdf, png, jpg |
Ma trận chi tiết, cách DocBuilder nhận biết định dạng nguồn và danh sách font có sẵn: xem Ma trận định dạng.
Gói và giới hạn#
Cả hai gói đều dùng được endpoint này. Các giới hạn liên quan:
| Hạng mục | Bản cộng đồng | Bản doanh nghiệp |
|---|---|---|
Cỡ tệp vào hoặc ra tối đa (max_file_mb) | 10 MB | 300 MB |
Thời gian chờ tối đa của chế độ đồng bộ (sync_timeout_seconds) | 60 giây | 60 giây |
Thời gian chạy tối đa của một job (job_timeout_seconds) | 120 giây | 600 giây |
Chế độ bất đồng bộ (async) | có | có |
Callback khi job xong (callback) | không | có, khi token có tính năng callback |
Mã lỗi thường gặp#
| HTTP | Mã | Khi nào |
|---|---|---|
| 400 | bad_request | Thiếu tham số, JSON hỏng, options sai kiểu, thiếu cả file lẫn url. detail.errors = [{path, message}]. |
| 401 | unauthorized | Thiếu hoặc sai khoá. detail.reason là missing, invalid hoặc no_keys_configured. |
| 403 | forbidden_feature | Gửi callback_url khi đang ở bản cộng đồng. |
| 413 | file_too_large | Tệp vào hoặc tệp ra vượt max_file_mb. detail.limit_mb. |
| 415 | unsupported_format | Không nhận ra định dạng nguồn, hoặc đổi khác họ (ví dụ docx sang xlsx). |
| 422 | corrupt_source | LibreOffice không mở được tệp nguồn. |
| 422 | password_required | Tệp có mật khẩu mà không truyền mật khẩu, hoặc mật khẩu sai. |
| 422 | url_not_allowed | URL vi phạm quy tắc chống SSRF. |
| 422 | download_failed | Không tải được URL nguồn. |
| 429 | rate_limited | Vượt hạn mức. detail.window là minute hoặc day; kèm Retry-After. |
| 503 | queue_full | Hàng đợi đã đầy (max_queued_jobs); kèm Retry-After. |
| 504 | timeout | Chế độ đồng bộ quá sync_timeout_seconds, hoặc job quá job_timeout_seconds. Worker bị dừng và dựng lại. |
options.password.{
"error": {
"code": "password_required",
"message": "Tệp có mật khẩu. Hãy gửi mật khẩu mở tệp trong options.password.",
"detail": {},
"request_id": "req_0123456789abcdef"
}
}Retry-After.{
"error": {
"code": "rate_limited",
"message": "Đã vượt hạn mức yêu cầu trong phút này.",
"detail": {
"window": "minute"
},
"request_id": "req_0123456789abcdef"
}
}