Platform
Licensing and editions
How the community and enterprise editions differ, loading the license token, offline operation, token states and a price summary.
On this page
O3O Office Online comes in two editions on the same installation: a free community edition and an enterprise edition priced per connection. Upgrading needs no reinstall: just load a license token.
How the editions differ#
| Item | Community | Enterprise |
|---|---|---|
| Price | Free | Per connection, see the price table below |
| License token | no | yes |
| Concurrent editing connections | 50 | Exactly conns from the token, even when below 50; orders are at least 50 |
| Editor embedding and save callbacks | yes | yes |
| DocBuilder endpoints | 10 endpoints, no template filling | All 11 endpoints (token with api_full) |
| DocBuilder rate | 10/minute, 200/day | 60–3,000/minute by connection count, no daily limit |
| Maximum DocBuilder file size | 10 MB | 300 MB |
| Job completion callbacks | no | yes (token with callback) |
| White label | no | Coming soon, source-built O3O image only |
| Soft overage | no | Coming soon |
Every limit in detail: Plan limits. How connections are counted: How connections are counted.
Loading the license token#
The gate and DocBuilder read the token in this order: the O3O_LICENSE_TOKEN variable when not empty, then the O3O_LICENSE_FILE file (default /etc/o3o/license.token) when it exists, otherwise no token (community edition). Leading and trailing whitespace is trimmed. The file is re-read every 60 seconds, so replacing it needs no restart; changing the environment variable requires recreating the container.
Put the token in place
Either paste it intoO3O_LICENSE_TOKENinonline/.env, or save it as a file mounted into botho3o-gateando3o-docbuilderat/etc/o3o/license.token.Load it
With the environment variable:docker compose ... up -d. With the file: wait up to 60 seconds.Check
GET /o3o/statusmust showlicense.state = "valid",edition = "enterprise"andconnections.limitequal to the purchased connections;GET /v1/limitsmust showedition = "enterprise".
# Option 1: environment variable in online/.env (token shortened for readability)
O3O_LICENSE_TOKEN=eyJ2IjoxLCJrZXkiOiJPT1NZLTdLMk05LVE0WEJDLUQ4SEpOLVIzVFdaIiwicGxhbiI6InllYXIiLCJwcm9kIjoibzNvLW9ubGluZSIs...base64...==.W2m7...base64...==
# Option 2: a file; leave O3O_LICENSE_TOKEN empty
O3O_LICENSE_TOKEN=
O3O_LICENSE_FILE=/etc/o3o/license.token
# Check after loading
curl -s http://localhost:8080/o3o/status | jq '{edition, license: .license.state, limit: .connections.limit}'
curl -s http://localhost:8080/v1/limits -H "Authorization: Bearer O3O_DEMO_KEY" | jq .edition# docker/compose.license.yml, use with: -f docker/compose.dev.yml -f docker/compose.license.yml
# ./license.token is relative to the first compose file's folder (online/docker/)
services:
o3o-gate:
volumes:
- ./license.token:/etc/o3o/license.token:ro
o3o-docbuilder:
volumes:
- ./license.token:/etc/o3o/license.token:roToken format#
A token is base64(payload_json) + "." + base64(signature): standard Base64 with = padding (alphabet +/), exactly one dot, and an Ed25519 signature over the exact payload bytes. The gate and DocBuilder verify it with the O3O license server's public key (O3O_LICENSE_PUBKEY). Unknown payload fields are ignored.
{
"v": 1,
"key": "OOSY-7K2M9-Q4XBC-D8HJN-R3TWZ",
"plan": "year",
"prod": "o3o-online",
"mid": "",
"cust": "Công ty TNHH An Phát",
"exp": "2027-09-30",
"upd": "2027-10-07",
"iat": 1790000000,
"lid": 123,
"kind": "enterprise",
"conns": 200,
"feat": [
"api_full",
"callback"
]
}Payload fields
vnumberrequiredFormat version:1or2.prodstringrequiredMust beo3o-online.kindstringrequiredenterpriseorcommunity; onlyenterpriseupgrades the edition.connsintegerrequiredPurchased connections, 1 or more. With a valid enterprise token the connection cap is exactly this number, even when it is below the community edition's 50.planstringrequiredyear(annual) orlife(perpetual).expstringoptionalYYYY-MM-DD, required foryear; valid until the END of that day, UTC+7.updstringoptionalYYYY-MM-DD: end of update rights. Images built after this date reject the token. For annual plans the license server setsupd = exp + 7 days(the grace period, matching the gate).featarrayoptionalDefault:[]Features:whitelabel,api_full(full DocBuilder),callback(DocBuilder callbacks).midstringoptionalBound instance id; empty, absent or*means unbound.keystringrequiredLicense code, shapedOOSY-XXXXX-XXXXX-XXXXX-XXXXX(annual) orOOSL-…(perpetual). The gate does not check the code's shape;/o3o/statusonly shows the last 5 characters (key_hint).custstringoptionalCustomer name.iatnumberrequiredSigning time, Unix seconds.lidnumberoptionalLicense server internal id; ignored by the gate and DocBuilder.aidnumberoptionalInternal activation id, present only when the token belongs to an activation; ignored by the gate and DocBuilder.
Token states#
license.state | Meaning | Effective edition |
|---|---|---|
none | No token. | community |
valid | Valid signature, not expired. | enterprise (when kind = enterprise) |
grace | Annual plan up to 7 days past exp; grace_days_left is the days remaining. | enterprise |
expired | More than 7 days past exp. | community |
invalid | Broken format, bad signature, bad mandatory fields, or a TEST key outside DEV mode. | community |
wrong_product | prod is not o3o-online. | community |
update_expired | The image was built after upd. | community |
instance_mismatch | The token is bound to another instance (mid). | community |
Working offline#
- The token is verified entirely OFFLINE with the Ed25519 public key in the configuration; your O3O server does not call office.o3o.vn and sends usage data nowhere.
- A server on an isolated network gets the full enterprise edition until
exp, plus 7 days of grace for annual plans. - Perpetual licenses never expire; update rights end at
upd: images built before that date keep accepting the token forever. - To bind a token to exactly one server, request a token whose
midequals theinstance_idfrom/o3o/status. When you use an instance-bound token, setO3O_GATE_INSTANCE_IDexplicitly in.envso the gate and DocBuilder share the same id;scripts/gen-secrets.sh(orgen-secrets.ps1) generates it when it creates a new.env.
Online activation with a license code, heartbeat and revocation through the office.o3o.vn server. v1 only verifies offline.
Renewing and adding connections#
O3O signs and delivers a new token once the order is complete. Replace the token the same way you loaded it; the gate picks it up within 60 seconds (file) or after the container is recreated (environment variable). Open sessions are not affected.
Price summary#
Prices are listed in USD. The unit price applies to ALL connections of the order (not progressive by tier). Minimum 50 connections per order. Perpetual licenses include 3 years of updates.
| Connections | Annual (USD/connection/year) | Perpetual with 3 years of updates (USD/connection) |
|---|---|---|
| 50 – 200 | 5 USD | 10 USD |
| 201 – 500 | 4.5 USD | 9 USD |
| 501 and above | 3.5 USD | 8 USD |
Never-more-expensive rule#
charged(n) = min(m × unit_price(m)) over every m ≥ n and m ≥ 50. When the cheapest price falls on m > n, the customer pays for m and IS GRANTED m connections. Buying more therefore never costs more at a tier boundary.
The invoice states, and the token grants, exactly the connection count of the level that is billed, not the number entered. Annual example: choosing 190 connections bills 201 connections × 4.5 USD = 904.5 USD, and the token carries conns = 201.
| Plan | Connection range | Amount charged | Billed and granted as |
|---|---|---|---|
| Annual | 181 – 200 | 904.5 USD | 201 |
| Annual | 390 – 500 | 1,753.5 USD | 501 |
| Perpetual | 181 – 200 | 1,809 USD | 201 |
| Perpetual | 446 – 500 | 4,008 USD | 501 |
| Connections | Annual: list | Annual: charged | Annual: granted | Perpetual: list | Perpetual: charged | Perpetual: granted |
|---|---|---|---|---|---|---|
| 50 | 250 USD | 250 USD | 50 | 500 USD | 500 USD | 50 |
| 120 | 600 USD | 600 USD | 120 | 1,200 USD | 1,200 USD | 120 |
| 190 | 950 USD | 904.5 USD | 201 | 1,900 USD | 1,809 USD | 201 |
| 200 | 1,000 USD | 904.5 USD | 201 | 2,000 USD | 1,809 USD | 201 |
| 201 | 904.5 USD | 904.5 USD | 201 | 1,809 USD | 1,809 USD | 201 |
| 300 | 1,350 USD | 1,350 USD | 300 | 2,700 USD | 2,700 USD | 300 |
| 450 | 2,025 USD | 1,753.5 USD | 501 | 4,050 USD | 4,008 USD | 501 |
| 500 | 2,250 USD | 1,753.5 USD | 501 | 4,500 USD | 4,008 USD | 501 |
| 501 | 1,753.5 USD | 1,753.5 USD | 501 | 4,008 USD | 4,008 USD | 501 |
| 800 | 2,800 USD | 2,800 USD | 800 | 6,400 USD | 6,400 USD | 800 |
Paying in VND: the USD to VND exchange rate is fixed and stored when the order is CREATED; the payment QR code keeps that exact amount. If an order expires unpaid, create a new order, priced at the exchange rate when the new order is created.