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

O3O DocBuilder và chuyển đổi

Trích văn bản, siêu dữ liệu và ảnh thu nhỏ

Ba endpoint đồng bộ lấy nội dung ra khỏi tài liệu: POST /v1/extract/text cho văn bản thuần theo từng trang, POST /v1/extract/meta cho siêu dữ liệu, POST /v1/extract/thumbnail cho ảnh một trang.

Trong trang này

Trích xuất luôn chạy đồng bộ, không tạo job và dùng được ở cả hai gói. Cả ba endpoint nhận mọi định dạng nguồn trong ma trận định dạng, gửi bằng multipart (trường file) hoặc JSON (trường url). Văn bản và ảnh thu nhỏ đi qua PDF trung gian rồi dùng pdftotext hoặc pdftoppm; siêu dữ liệu được đọc qua UNO mà không dựng trang.

Trích văn bản#

POST/v1/extract/text

Trích văn bản thuần, theo từng trang.

Xác thực: BearerCộng đồngDoanh nghiệp

Thân multipart/form-data

  • filefilebắt buộc
    Tệp nguồn.
  • fromstringtuỳ chọn
    Định dạng nguồn khi tên tệp không có đuôi rõ ràng.
  • passwordstringtuỳ chọn
    Mật khẩu mở tệp nguồn.
  • max_charsinteger 1–5.000.000tuỳ chọnMặc định: 1000000
    Cắt văn bản trả về ở số ký tự này.

Thân application/json

  • urlURL, ≤ 2048bắt buộc
    URL tải tệp nguồn, chịu quy tắc chống SSRF.
  • fromstringtuỳ chọn
    Định dạng nguồn khi tên tệp không có đuôi rõ ràng.
  • passwordstringtuỳ chọn
    Mật khẩu mở tệp nguồn.
  • max_charsinteger 1–5.000.000tuỳ chọnMặc định: 1000000
    Cắt văn bản trả về ở số ký tự này.
Trích văn bản của một tệp docx
curl -sS http://localhost:8080/v1/extract/text \
  -H "Authorization: Bearer O3O_DEMO_KEY" \
  -F "file=@bao-gia.docx"
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-gia.docx")]), "bao-gia.docx");

const res = await fetch(`${BASE_URL}/v1/extract/text`, {
  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-gia.docx", "rb") as f:
    r = requests.post(
        f"{BASE_URL}/v1/extract/text",
        headers=HEADERS,
        files={"file": ("bao-gia.docx", f)},
        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/extract/text");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY"],
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile('bao-gia.docx'),
    ],
    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-gia.docx")), "file", "bao-gia.docx");

using var res = await http.PostAsync("http://localhost:8080/v1/extract/text", form);
if (!res.IsSuccessStatusCode)
    throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
Console.WriteLine(await res.Content.ReadAsStringAsync());
200Văn bản đầy đủ và từng trang (ví dụ đã rút gọn nội dung).
{
  "text": "BÁO GIÁ BẢN QUYỀN\nSố: BG-2026-0917  ·  Ngày lập: 21/09/2026\n1. Thông tin khách hàng\nKính gửi: Công ty TNHH Thương mại và Dịch vụ An Phát\n\nPhụ lục: Cách đếm kết nối\nMột người mở ba tài liệu ở chế độ sửa được tính là ba kết nối.",
  "pages": [
    {
      "page": 1,
      "text": "BÁO GIÁ BẢN QUYỀN\nSố: BG-2026-0917  ·  Ngày lập: 21/09/2026\n1. Thông tin khách hàng\nKính gửi: Công ty TNHH Thương mại và Dịch vụ An Phát"
    },
    {
      "page": 2,
      "text": "Phụ lục: Cách đếm kết nối\nMột người mở ba tài liệu ở chế độ sửa được tính là ba kết nối."
    }
  ],
  "page_count": 2,
  "chars": 226,
  "truncated": false
}
  • text là toàn bộ văn bản, các trang nối bằng hai dấu xuống dòng, bị cắt ở max_chars.
  • chars là số ký tự của văn bản ĐẦY ĐỦ trước khi cắt; truncated cho biết đã cắt hay chưa.
  • pages cho văn bản từng trang, tiện đánh chỉ mục tìm kiếm theo trang.

Lỗi thường gặp: 400 bad_request khi thiếu cả file lẫn url hoặc max_chars ngoài khoảng cho phép, 415 unsupported_format, 422 corrupt_source, 422 password_required. Bảng đầy đủ ở cuối trang.

Siêu dữ liệu#

POST/v1/extract/meta

Trích siêu dữ liệu và số liệu thống kê của tài liệu.

Xác thực: BearerCộng đồngDoanh nghiệp

Thân multipart/form-data

  • filefilebắt buộc
    Tệp nguồn.
  • fromstringtuỳ chọn
    Định dạng nguồn khi tên tệp không có đuôi rõ ràng.
  • passwordstringtuỳ chọn
    Mật khẩu mở tệp nguồn.

Thân application/json

  • urlURL, ≤ 2048bắt buộc
    URL tải tệp nguồn, chịu quy tắc chống SSRF.
  • fromstringtuỳ chọn
    Định dạng nguồn khi tên tệp không có đuôi rõ ràng.
  • passwordstringtuỳ chọn
    Mật khẩu mở tệp nguồn.
Siêu dữ liệu của một bảng tính lấy từ URL
curl -sS http://localhost:8080/v1/extract/meta \
  -H "Authorization: Bearer O3O_DEMO_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/files/bang-luong.xlsx"}'
const BASE_URL = "http://localhost:8080";
const HEADERS = { Authorization: "Bearer O3O_DEMO_KEY" };

const payload = {"url": "https://example.com/files/bang-luong.xlsx"};

const res = await fetch(`${BASE_URL}/v1/extract/meta`, {
  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()}`);
console.log(await res.json());
import requests

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

payload = {"url": "https://example.com/files/bang-luong.xlsx"}
r = requests.post(f"{BASE_URL}/v1/extract/meta", headers=HEADERS, json=payload, timeout=90)
if not r.ok:
    raise RuntimeError(f"{r.status_code}: {r.text}")
print(r.json())
<?php
$payload = json_encode(['url' => 'https://example.com/files/bang-luong.xlsx'], JSON_UNESCAPED_UNICODE);

$ch = curl_init("http://localhost:8080/v1/extract/meta");
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));
}
print_r(json_decode($body, true));
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/bang-luong.xlsx"
    }
    """;
using var res = await http.PostAsync("http://localhost:8080/v1/extract/meta",
    new StringContent(json, Encoding.UTF8, "application/json"));
if (!res.IsSuccessStatusCode)
    throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
Console.WriteLine(await res.Content.ReadAsStringAsync());
200Bảng tính: sheets liệt kê tên trang tính; các số liệu của văn bản là null.
{
  "family": "sheet",
  "format": "xlsx",
  "size": 9876,
  "title": "Bảng lương tháng 9 năm 2026",
  "subject": null,
  "author": "Phòng Hành chính Nhân sự",
  "description": null,
  "keywords": [],
  "created": "2026-09-21T10:00:00Z",
  "modified": "2026-09-21T10:00:00Z",
  "generator": "LibreOffice/7.4.7.2$Linux_X86_64",
  "pages": null,
  "words": null,
  "chars": null,
  "paragraphs": null,
  "sheets": [
    "Bảng lương",
    "Theo phòng ban"
  ],
  "slides": null
}

Trường của kết quả

  • familytext | sheet | slidebắt buộc
    Họ tài liệu.
  • formatstringbắt buộc
    Định dạng nguồn đã nhận ra.
  • sizeintegerbắt buộc
    Kích thước tệp nguồn, byte.
  • title, subject, author, descriptionstring | nulltuỳ chọn
    Thuộc tính tài liệu.
  • keywordsarraytuỳ chọn
    Từ khoá.
  • created, modifieddate-time | nulltuỳ chọn
    Thời điểm tạo và sửa.
  • generatorstring | nulltuỳ chọn
    Phần mềm đã tạo tệp.
  • pagesinteger | nulltuỳ chọn
    Số trang (họ text), null với họ khác.
  • words, chars, paragraphsinteger | nulltuỳ chọn
    Số liệu thống kê của tài liệu.
  • sheetsarraytuỳ chọn
    Tên các trang tính (họ sheet), mảng rỗng với họ khác.
  • slidesinteger | nulltuỳ chọn
    Số trang chiếu (họ slide).

Lỗi thường gặp: 401 unauthorized, 413 file_too_large, 415 unsupported_format, 422 corrupt_source, 422 password_required; với nguồn URL thêm 422 url_not_allowed422 download_failed.

Ảnh thu nhỏ#

POST/v1/extract/thumbnail

Ảnh PNG hoặc JPG của một trang, trả thẳng ảnh.

Xác thực: BearerCộng đồngDoanh nghiệp

Thân multipart/form-data

  • filefilebắt buộc
    Tệp nguồn.
  • fromstringtuỳ chọn
    Định dạng nguồn khi tên tệp không có đuôi rõ ràng.
  • passwordstringtuỳ chọn
    Mật khẩu mở tệp nguồn.
  • pageinteger ≥ 1tuỳ chọnMặc định: 1
    Trang cần dựng ảnh.
  • widthinteger 16–2000tuỳ chọnMặc định: 320
    Chiều rộng ảnh, điểm ảnh.
  • formatpng | jpgtuỳ chọnMặc định: png
    Định dạng ảnh.

Thân application/json

  • urlURL, ≤ 2048bắt buộc
    URL tải tệp nguồn, chịu quy tắc chống SSRF.
  • fromstringtuỳ chọn
    Định dạng nguồn khi tên tệp không có đuôi rõ ràng.
  • passwordstringtuỳ chọn
    Mật khẩu mở tệp nguồn.
  • pageinteger ≥ 1tuỳ chọnMặc định: 1
    Trang cần dựng ảnh.
  • widthinteger 16–2000tuỳ chọnMặc định: 320
    Chiều rộng ảnh, điểm ảnh.
  • formatpng | jpgtuỳ chọnMặc định: png
    Định dạng ảnh.
Ảnh trang bìa rộng 320 điểm ảnh
curl -sS http://localhost:8080/v1/extract/thumbnail \
  -H "Authorization: Bearer O3O_DEMO_KEY" \
  -F "file=@gioi-thieu.pptx" \
  -F "page=1" \
  -F "width=320" \
  -F "format=png" \
  -D - \
  -o trang-bia.png -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("gioi-thieu.pptx")]), "gioi-thieu.pptx");
form.append("page", "1");
form.append("width", "320");
form.append("format", "png");

const res = await fetch(`${BASE_URL}/v1/extract/thumbnail`, {
  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-bia.png", Buffer.from(await res.arrayBuffer()));
console.log("X-O3O-Page-Count:", res.headers.get("X-O3O-Page-Count"));
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/extract/thumbnail",
        headers=HEADERS,
        files={"file": ("gioi-thieu.pptx", f)},
        data={
            "page": "1",
            "width": "320",
            "format": "png",
        },
        timeout=90,
    )
if not r.ok:
    raise RuntimeError(f"{r.status_code}: {r.text}")
with open("trang-bia.png", "wb") as fh:
    fh.write(r.content)
print("X-O3O-Page-Count:", r.headers.get("X-O3O-Page-Count"))
<?php
$headers = [];
$ch = curl_init("http://localhost:8080/v1/extract/thumbnail");
curl_setopt_array($ch, [
    CURLOPT_HTTPHEADER => ["Authorization: Bearer O3O_DEMO_KEY"],
    CURLOPT_POSTFIELDS => [
        'file' => new CURLFile('gioi-thieu.pptx'),
        'page' => '1',
        'width' => '320',
        'format' => 'png',
    ],
    CURLOPT_HEADERFUNCTION => function ($ch, $line) use (&$headers) {
        $parts = explode(":", $line, 2);
        if (count($parts) === 2) {
            $headers[strtolower(trim($parts[0]))] = trim($parts[1]);
        }
        return strlen($line);
    },
    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("trang-bia.png", $body);
echo "X-O3O-Page-Count: " . ($headers["x-o3o-page-count"] ?? "") . "\n";
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("gioi-thieu.pptx")), "file", "gioi-thieu.pptx");
form.Add(new StringContent("1"), "page");
form.Add(new StringContent("320"), "width");
form.Add(new StringContent("png"), "format");

using var res = await http.PostAsync("http://localhost:8080/v1/extract/thumbnail", form);
if (!res.IsSuccessStatusCode)
    throw new HttpRequestException($"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync()}");
await File.WriteAllBytesAsync("trang-bia.png", await res.Content.ReadAsByteArrayAsync());
Console.WriteLine("X-O3O-Page-Count: " + string.Join(",", res.Headers.GetValues("X-O3O-Page-Count")));
200Thân là ảnh. Header X-O3O-Page-Count cho biết tổng số trang, tiện dựng nút chuyển trang khi hiển thị ảnh thu nhỏ.
{
  "X-O3O-Request-Id": "req_0123456789abcdef",
  "X-O3O-Page-Count": "3",
  "X-RateLimit-Limit": "10",
  "X-RateLimit-Remaining": "9",
  "X-RateLimit-Reset": "1789984860"
}

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

HTTPKhi nào
400bad_requestThiếu cả file lẫn url, hoặc page vượt số trang.
401unauthorizedThiếu hoặc sai khoá. detail.reasonmissing, invalid hoặc no_keys_configured.
413file_too_largeTệp vào hoặc tệp ra vượt max_file_mb. detail.limit_mb.
415unsupported_formatKhông nhận ra định dạng nguồn, hoặc cặp nguồn và đích không được hỗ trợ.
422corrupt_sourceLibreOffice không mở được tệp nguồn.
422password_requiredTệp có mật khẩu mà không truyền mật khẩu, hoặc mật khẩu sai.
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.
503pool_unavailableKhông còn worker nào sống.
504timeoutChế độ đồng bộ quá sync_timeout_seconds, hoặc job quá job_timeout_seconds. Worker bị dừng và dựng lại.
400Xin trang 9 của tài liệu 3 trang.
{
  "error": {
    "code": "bad_request",
    "message": "Tham số không hợp lệ.",
    "detail": {
      "errors": [
        {
          "path": "/page",
          "message": "Trang 9 vượt số trang của tài liệu (3)."
        }
      ]
    },
    "request_id": "req_0123456789abcdef"
  }
}

Sắp có#

Sắp có

Trích cấu trúc tài liệu (/v1/extract/structure), trích ảnh nhúng (/v1/extract/media) và so sánh hai tài liệu (/v1/compare).