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ói | Trần kết nối đồng thời | Ghi chú |
|---|---|---|
| Bản cộng đồng | 50 | Khô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ức | Bản cộng đồng | Bản doanh nghiệp |
|---|---|---|
Yêu cầu tính phí mỗi phút (rate_per_minute) | 10 | 60 × ⌈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) | 200 | không giới hạn |
Job chạy song song (parallel_jobs) | 1 | bằng số worker (O3O_DOCBUILDER_WORKERS) |
Job chờ tối đa trong hàng đợi (max_queued_jobs) | 10 | 1.000 |
Cỡ tệp vào hoặc ra tối đa (max_file_mb) | 10 MB | 300 MB |
Thời gian giữ kết quả (result_ttl_minutes) | 15 phút | 1.440 phút |
Thời gian chờ của chế độ đồng bộ (sync_timeout_seconds) | 60 giây | 60 giây |
Thời gian 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ó |
Đơn vị kịch bản o3oscript tối đa (max_script_units) | 500 | 20.000 |
Số tệp ra tối đa của một lần dựng (max_outputs_per_build) | 1 | 5 |
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 token | Yêu cầu mỗi phút |
|---|---|
| 50 | 60 |
| 120 | 180 |
| 200 | 240 |
| 201 | 300 |
| 500 | 600 |
| 800 | 960 |
| 2.500 | 3.000 |
| 5.000 | 3.000 |
Endpoint theo gói#
| Endpoint | Bản cộng đồng | Bản doanh nghiệp |
|---|---|---|
GET /v1/status | có | có |
GET /v1/formats | có | có |
GET /v1/limits | có | có |
POST /v1/convert | có | có |
GET /v1/jobs/{id} | có | có |
GET /v1/files/{id} | có | có |
POST /v1/build | có | có |
POST /v1/template/render | không | có |
POST /v1/extract/text | có | có |
POST /v1/extract/meta | có | có |
POST /v1/extract/thumbnail | có | có |
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ối401hoặc403trướ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#
| Header | Nội dung |
|---|---|
X-RateLimit-Limit | Số yêu cầu tính phí được phép mỗi phút |
X-RateLimit-Remaining | Số còn lại trong phút hiện tại |
X-RateLimit-Reset | Giây Unix khi cửa sổ phút đặt lại |
X-RateLimit-Limit-Day | Số 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-Day | Số còn lại trong ngày; vắng mặt khi gói không giới hạn theo ngày |
Retry-After | Chỉ có ở 429 và 503: số giây nên chờ |
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: 184Xử lý 429 và 503#
detail.window là minute 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"
}
}- Đọc
Retry-Aftervà chờ đúng số giây đó; đừng gửi dồn. 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ờ.503 queue_fullcũng cóRetry-After: giảm số yêu cầu song song.- Theo dõi
X-RateLimit-Remainingđể tự giãn nhịp trước khi chạm trần. - Tệp lớn hoặc nhiều tệp: dùng
async = true, rồi hỏiGET /v1/jobs/{id}(không bị tính).
// 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#
/v1/limitsHạ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.
{
"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": []
}/o3o/limitsHạ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_jobs là null; số thật xem ở GET /v1/limits.
{
"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
}
}