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

Nền tảng

Hạn mức theo gói

Hạn mức kết nối và DocBuilder của bản cộng đồng và bản doanh nghiệp lấy từ plans.json, header giới hạn tần suất và cách xử lý 429.

Trong trang này

Mọi con số trên trang này được sinh từ tệp plans.json (cập nhật 2026-09-21), cùng tệp mà gate và DocBuilder đọc lúc chạy.

Kết nối soạn thảo#

GóiTrần kết nối đồng thờiGhi chú
Bản cộng đồng50Không cần token bản quyền.
Bản doanh nghiệpđúng bằng conns trong tokenĐơn mua tối thiểu 50 kết nối. Trần bằng đúng conns kể cả khi nhỏ hơn 50. Vượt mềm: sắp có.

Trần 50 chỉ áp cho bản cộng đồng. Với token doanh nghiệp hợp lệ, trần là đúng số conns trong token, kể cả khi số đó nhỏ hơn 50 (ví dụ mã dùng thử hoặc nội bộ do O3O cấp với conns = 10 cho trần 10, không được nâng lên 50). O3O_GATE_CONNECTION_CAP chỉ hạ được trần này.

Cách đếm, đỉnh trượt và hành vi khi chạm trần: Kết nối được đếm thế nào.

Hạn mức DocBuilder#

Hạn mứcBản cộng đồngBản doanh nghiệp
Yêu cầu tính phí mỗi phút (rate_per_minute)1060 × ⌈số kết nối / 50⌉, tối thiểu 60, tối đa 3.000
Yêu cầu tính phí mỗi ngày (rate_per_day)200không giới hạn
Job chạy song song (parallel_jobs)1bằng số worker (O3O_DOCBUILDER_WORKERS)
Job chờ tối đa trong hàng đợi (max_queued_jobs)101.000
Cỡ tệp vào hoặc ra tối đa (max_file_mb)10 MB300 MB
Thời gian giữ kết quả (result_ttl_minutes)15 phút1.440 phút
Thời gian chờ của chế độ đồng bộ (sync_timeout_seconds)60 giây60 giây
Thời gian tối đa của một job (job_timeout_seconds)120 giây600 giây
Chế độ bất đồng bộ (async)
Callback khi job xong (callback)không
Đơn vị kịch bản o3oscript tối đa (max_script_units)50020.000
Số tệp ra tối đa của một lần dựng (max_outputs_per_build)15

Tần suất của bản doanh nghiệp theo số kết nối#

Công thức: clamp(60 * ceil(conns / 50), 60, 3000).

Kết nối trong tokenYêu cầu mỗi phút
5060
120180
200240
201300
500600
800960
2.5003.000
5.0003.000

Endpoint theo gói#

EndpointBản cộng đồngBản doanh nghiệp
GET /v1/status
GET /v1/formats
GET /v1/limits
POST /v1/convert
GET /v1/jobs/{id}
GET /v1/files/{id}
POST /v1/build
POST /v1/template/renderkhông
POST /v1/extract/text
POST /v1/extract/meta
POST /v1/extract/thumbnail

Endpoint ngoài danh sách của gói trả 403 forbidden_feature. GET /v1/status không cần xác thực.

Lớp nhúng trình soạn thảo#

Hai gói đều nhúng được trình soạn thảo và nhận callback lưu. Cỡ tài liệu nhúng tối đa đặt bằng O3O_EMBED_MAX_FILE_MB (mặc định 100 MB); thời hạn phiên đặt bằng O3O_EMBED_SESSION_TTL_MINUTES (mặc định 720 phút).

Phạm vi đếm tần suất#

  • Hạn mức tính chung cho CẢ instance DocBuilder, không theo từng khoá API.
  • Yêu cầu bị tính: POST /v1/convert, POST /v1/build, POST /v1/template/render, POST /v1/extract/text, POST /v1/extract/meta, POST /v1/extract/thumbnail.
  • Không tính: mọi yêu cầu GET, và yêu cầu bị từ chối 401 hoặc 403 trước khi vào hàng đợi.
  • Cửa sổ phút là phút đồng hồ theo UTC (cửa sổ cố định).
  • Cửa sổ ngày đặt lại lúc 00:00 giờ Việt Nam (UTC+7).

Header giới hạn tần suất#

HeaderNội dung
X-RateLimit-LimitSố yêu cầu tính phí được phép mỗi phút
X-RateLimit-RemainingSố còn lại trong phút hiện tại
X-RateLimit-ResetGiây Unix khi cửa sổ phút đặt lại
X-RateLimit-Limit-DaySố yêu cầu mỗi ngày; vắng mặt khi gói không giới hạn theo ngày
X-RateLimit-Remaining-DaySố còn lại trong ngày; vắng mặt khi gói không giới hạn theo ngày
Retry-AfterChỉ có ở 429503: số giây nên chờ
HTTPHeader của một phản hồi ở bản cộng đồng
HTTP/1.1 200 OK
X-O3O-Request-Id: req_0123456789abcdef
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 7
X-RateLimit-Reset: 1790000060
X-RateLimit-Limit-Day: 200
X-RateLimit-Remaining-Day: 184

Xử lý 429 và 503#

429Vượt hạn mức phút. detail.windowminute hoặc day; kèm header Retry-After.
{
  "error": {
    "code": "rate_limited",
    "message": "Đã vượt hạn mức số yêu cầu mỗi phút.",
    "detail": {
      "window": "minute"
    },
    "request_id": "req_0123456789abcdef"
  }
}
  1. Đọc Retry-After và chờ đúng số giây đó; đừng gửi dồn.
  2. detail.window = "day": hết lượt trong ngày; dời việc sang sau 00:00 giờ Việt Nam thay vì giữ luồng chờ.
  3. 503 queue_full cũng có Retry-After: giảm số yêu cầu song song.
  4. Theo dõi X-RateLimit-Remaining để tự giãn nhịp trước khi chạm trần.
  5. Tệp lớn hoặc nhiều tệp: dùng async = true, rồi hỏi GET /v1/jobs/{id} (không bị tính).
Gọi có thử lại theo Retry-After
// Node.js 18+, tệp .mjs
import { readFile } from "node:fs/promises";

export async function callWithRetry(url, options, maxAttempts = 5) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(url, options);
    if ((res.status !== 429 && res.status !== 503) || attempt >= maxAttempts) return res;
    if (res.status === 429) {
      const body = await res.clone().json().catch(() => null);
      if (body?.error?.detail?.window === "day") return res; // hết lượt trong ngày: đừng chờ trong luồng này, hẹn lại sau 00:00 giờ Việt Nam
    }
    const retryAfter = res.headers.get("Retry-After");
    const wait = retryAfter !== null ? Number(retryAfter) : 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  }
}

const form = new FormData();
form.append("file", new Blob([await readFile("bao-cao.docx")]), "bao-cao.docx"); // đọc thành byte để gửi lại được khi thử lại
form.append("to", "pdf");
const res = await callWithRetry("http://localhost:8080/v1/convert", {
  method: "POST",
  headers: { Authorization: "Bearer O3O_DEMO_KEY" },
  body: form
});
# pip install requests
import time

import requests


def call_with_retry(method: str, url: str, max_attempts: int = 5, **kwargs) -> requests.Response:
    for attempt in range(1, max_attempts + 1):
        r = requests.request(method, url, timeout=120, **kwargs)
        if r.status_code not in (429, 503) or attempt == max_attempts:
            return r
        if r.status_code == 429 and r.json()["error"]["detail"].get("window") == "day":
            return r  # hết lượt trong ngày: đừng chờ trong luồng này, hẹn lại sau 00:00 giờ Việt Nam
        time.sleep(int(r.headers.get("Retry-After", 2 ** attempt)))
    return r


with open("bao-cao.docx", "rb") as fh:
    data = fh.read()  # đọc thành byte để gửi lại được khi thử lại
r = call_with_retry(
    "POST", "http://localhost:8080/v1/convert",
    headers={"Authorization": "Bearer O3O_DEMO_KEY"},
    files={"file": ("bao-cao.docx", data)},
    data={"to": "pdf"},
)
using System.Net;
using System.Net.Http.Headers;
using System.Text.Json;

using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(120) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "O3O_DEMO_KEY");
byte[] data = await File.ReadAllBytesAsync("bao-cao.docx");

HttpResponseMessage res;
for (int attempt = 1; ; attempt++)
{
    using var form = new MultipartFormDataContent(); // tạo form mới cho mỗi lần gửi
    form.Add(new ByteArrayContent(data), "file", "bao-cao.docx");
    form.Add(new StringContent("pdf"), "to");
    res = await http.PostAsync("http://localhost:8080/v1/convert", form);
    bool retryable = res.StatusCode == HttpStatusCode.TooManyRequests || res.StatusCode == HttpStatusCode.ServiceUnavailable;
    if (!retryable || attempt >= 5) break;
    if (res.StatusCode == HttpStatusCode.TooManyRequests)
    {
        using JsonDocument err = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
        if (err.RootElement.GetProperty("error").GetProperty("detail").TryGetProperty("window", out JsonElement w) &&
            w.GetString() == "day") break; // hết lượt trong ngày: đừng chờ trong luồng này, hẹn lại sau 00:00 giờ Việt Nam
    }
    TimeSpan wait = res.Headers.RetryAfter?.Delta ?? TimeSpan.FromSeconds(Math.Pow(2, attempt));
    await Task.Delay(wait);
}

Xem hạn mức đang áp#

GET/v1/limits

Hạn mức hiệu lực của DocBuilder (đã áp biến môi trường chỉ-hạ) và mức đã dùng. Không bị tính vào hạn mức.

Xác thực: BearerCộng đồngDoanh nghiệp
200Ví dụ ở bản cộng đồng; số đã dùng là minh hoạ.
{
  "edition": "community",
  "limits": {
    "rate_per_minute": 10,
    "rate_per_day": 200,
    "parallel_jobs": 1,
    "max_queued_jobs": 10,
    "max_file_mb": 10,
    "result_ttl_minutes": 15,
    "sync_timeout_seconds": 60,
    "job_timeout_seconds": 120,
    "async": true,
    "callback": false,
    "max_script_units": 500,
    "max_outputs_per_build": 1
  },
  "usage": {
    "minute": {
      "limit": 10,
      "used": 3,
      "remaining": 7,
      "reset_at": "2026-09-21T10:01:00Z"
    },
    "day": {
      "limit": 200,
      "used": 16,
      "remaining": 184,
      "reset_at": "2026-09-21T17:00:00Z"
    },
    "running_jobs": 0,
    "queued_jobs": 0
  },
  "allowed_endpoints": [
    "GET /v1/status",
    "GET /v1/formats",
    "GET /v1/limits",
    "POST /v1/convert",
    "GET /v1/jobs/{id}",
    "GET /v1/files/{id}",
    "POST /v1/build",
    "POST /v1/extract/text",
    "POST /v1/extract/meta",
    "POST /v1/extract/thumbnail"
  ],
  "features": []
}
GET/o3o/limits

Hạn mức của gói hiện hành theo gate: kết nối, lớp nhúng và DocBuilder. Không cần xác thực. Với bản doanh nghiệp, docbuilder.parallel_jobsnull; số thật xem ở GET /v1/limits.

Xác thực: không cầnCộng đồngDoanh nghiệp
200Ví dụ ở bản cộng đồng.
{
  "edition": "community",
  "connections": {
    "limit": 50,
    "plan_limit": 50,
    "cap": null,
    "min_per_order": 50
  },
  "whitelabel": false,
  "features": [],
  "editor_embed": {
    "enabled": true,
    "save_callback": true,
    "max_file_mb": 100,
    "session_ttl_minutes": 720
  },
  "docbuilder": {
    "allowed_endpoints": [
      "GET /v1/status",
      "GET /v1/formats",
      "GET /v1/limits",
      "POST /v1/convert",
      "GET /v1/jobs/{id}",
      "GET /v1/files/{id}",
      "POST /v1/build",
      "POST /v1/extract/text",
      "POST /v1/extract/meta",
      "POST /v1/extract/thumbnail"
    ],
    "rate_per_minute": 10,
    "rate_per_day": 200,
    "parallel_jobs": 1,
    "max_queued_jobs": 10,
    "max_file_mb": 10,
    "result_ttl_minutes": 15,
    "sync_timeout_seconds": 60,
    "job_timeout_seconds": 120,
    "async": true,
    "callback": false,
    "max_script_units": 500,
    "max_outputs_per_build": 1
  },
  "counting": {
    "sample_interval_seconds": 10,
    "peak_window_seconds": 300,
    "reconnect_grace_seconds": 120
  }
}