Platform
How connections are counted
One connection is one concurrent editing session: counting, the 5-minute sliding peak, reconnect grace, what happens at the cap, and GET /o3o/status.
On this page
1 connection = 1 concurrent editing session: one view with edit rights open on the editing server. Read-only sessions and all DocBuilder work are not counted.
| Situation | Connections |
|---|---|
| One person opens 3 documents for editing | 3 |
| Three people edit the same document | 3 |
| One person opens the same document in two browser tabs, both editing | 2 |
| 1 person editing, 2 people viewing the same document read-only | 1 |
Embedded O3O.Editor with mode: "view" | 0 |
Conversion, building or extraction through DocBuilder /v1/* | 0 |
Also not counted: health checks, /hosting/discovery, /hosting/capabilities, and Nextcloud rendering previews through /cool/convert-to on the internal network. /cool/convert-to is not exposed through o3o-proxy (it returns 404); external systems convert with POST /v1/convert, which is not counted either.
How counting works#
Sampling
Every 10 seconds the gate asks the editing server's internal admin channel for the open views and each view's read-only flag. The number of editing views in the latest sample iscurrent. The gate stores neither file names nor user names.Just-admitted sessions
Editing sessions admitted but not yet visible in a sample count aspending, so a burst of openings cannot overshoot the cap. Apendingentry clears once a sample confirms it, or after 60 seconds.Enforcing the cap
A NEW editing session is admitted whencurrent + pending < limit.limitis 50 on the community edition; with a valid enterprise token,limitis exactly the token'sconns, even when that number is below 50.O3O_GATE_CONNECTION_CAPcan only lower the cap. When someone leaves, the slot is usable from the next sample.5 minutes sliding peak
peak_5mis the highestcurrentover the last 300 seconds. It is used for REPORTING and reconciliation, never for refusing sessions.Reconnect grace
When a document's editing views drop, the gate keeps a 120-second grace slot for that document. Someone who briefly lost the network or reloaded the page gets back into that document even if the cap is full.
When the cap is reached#
| Situation | v1 behaviour |
|---|---|
| Sessions already open | Untouched, even when the cap is lowered (token expired or replaced). |
| Saving, automatic or manual | Never blocked. |
| Reconnecting within 120 seconds | Admitted. |
| New read-only session | Always admitted, not counted. |
| New editing session through Nextcloud | The editor page is replaced by a Vietnamese "connection limit reached" page (HTTP 429) with Open read-only and Try again buttons. |
New embedded session with mode: "edit" | Opens read-only and returns limited: true; api.js shows a banner and fires onLimitReached. |
{
"error": {
"code": "connection_limit_reached",
"message": "Hệ thống đã đạt giới hạn 50 phiên soạn thảo đồng thời của bản cộng đồng. Tài liệu của bạn vẫn an toàn. Bạn có thể mở ở chế độ chỉ đọc hoặc thử lại sau ít phút.",
"detail": {
"limit": 50,
"current": 50,
"edition": "community"
},
"request_id": "req_0123456789abcdef"
}
}Soft overage for the enterprise edition (controlled headroom above the cap) is not in v1.
When counting is unavailable#
The gate fails open: when there is no fresh sample (older than 3 periods), connections.enforcing becomes false and every session is admitted. If the gate itself stops, the proxy lets requests through as well. Users are never blocked because the counter failed. Failing open only applies when the figures source or the gate itself fails: a WebSocket request the gate cannot parse is unknown, not a counter failure, so it is always refused.
Reading the figures: GET /o3o/status#
/o3o/statusGate, edition, license and connection status. No authentication; contains no secrets, file names or user names.
{
"service": "o3o-gate",
"version": "1.0.0",
"api": "v1",
"time": "2026-09-21T10:00:00Z",
"instance_id": "inst_3f9a1c0b7d2e4a55",
"edition": "community",
"dev_mode": false,
"dev_image": true,
"dev_image_note": "Image soạn thảo là bản thượng nguồn, CHỈ THỬ NỘI BỘ, không phải bản O3O phát hành.",
"license": {
"state": "none",
"kind": null,
"plan": null,
"conns": null,
"exp": null,
"upd": null,
"customer": null,
"key_hint": null,
"grace_days_left": null,
"message": "Không có token bản quyền. Đang chạy bản cộng đồng."
},
"connections": {
"limit": 50,
"current": 3,
"pending": 0,
"peak_5m": 4,
"readonly": 2,
"views_total": 5,
"documents": 2,
"limit_reached": false,
"source": "adminws",
"sampled_at": "2026-09-21T09:59:55Z",
"sample_age_seconds": 5,
"enforcing": true
},
"upstream": {
"coolwsd": "ok",
"coolwsd_version": "26.04.4.1"
},
"embed": {
"enabled": true,
"jwt_required": true,
"callback_signing": true,
"open_documents": 1,
"sessions": 2
}
}Fields that are easy to misread
connections.limitintegerrequiredEffective cap = the smaller of the plan cap andO3O_GATE_CONNECTION_CAP.connections.currentintegerrequiredEditing views in the latest sample.connections.pendingintegerrequiredEditing sessions admitted but not yet sampled.connections.peak_5mintegerrequiredPeak ofcurrentwithin the sliding window.connections.readonlyintegerrequiredRead-only views (not counted).connections.limit_reachedbooleanrequiredcurrent + pending >= limit.connections.sourcestringrequiredadminwswhen counting works;nonewhen no fresh sample exists.connections.enforcingbooleanrequiredfalsemeans the gate is failing open.upstream.coolwsdstringrequiredok,down(unreachable),auth_failed(wrong admin credentials).dev_imagebooleanrequiredtruewhen the editing server runs the DEV image, for internal testing only.
# The connections block
curl -s http://localhost:8080/o3o/status | jq .connections
# Refresh every 10 seconds
watch -n 10 'curl -s http://localhost:8080/o3o/status | jq -c "{current: .connections.current, peak_5m: .connections.peak_5m, limit: .connections.limit}"'# pip install requests
import requests
s = requests.get("http://localhost:8080/o3o/status", timeout=10).json()
c = s["connections"]
print(f"{c['current'] + c['pending']}/{c['limit']} ({s['edition']}), peak_5m={c['peak_5m']}")
if not c["enforcing"]:
print("Warning: the counter is failing open")
if c["limit_reached"]:
print("Cap reached: new editing sessions will be refused")Usage log for reconciliation#
Every 5 minutes the gate appends a line to /data/usage/usage-YYYY-MM.csv in the o3o-gate-data volume: ts_utc,peak_edit,peak_views_total,limit,edition, with a header line. Use it to reconcile usage yourself before buying more connections.
# Run inside online/; the figures below are illustrative
docker compose --env-file .env -f docker/compose.dev.yml cp o3o-gate:/data/usage ./usage
head -3 ./usage/usage-2026-09.csv
# ts_utc,peak_edit,peak_views_total,limit,edition
# 2026-09-21T10:00:00Z,4,6,50,community
# 2026-09-21T10:05:00Z,7,9,50,community30-day P95 and a usage chart page.
Testing the cap on a DEV machine#
The editing server's DEV image has its own hard cap of 20 connections and 10 documents, so the 50 mark cannot be tested on a DEV machine. Set O3O_GATE_CONNECTION_CAP=2 and open three editing sessions: the third gets the limit page while read-only sessions still get in.