Skip to content

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.

SituationConnections
One person opens 3 documents for editing3
Three people edit the same document3
One person opens the same document in two browser tabs, both editing2
1 person editing, 2 people viewing the same document read-only1
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#

  1. 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 is current. The gate stores neither file names nor user names.
  2. Just-admitted sessions

    Editing sessions admitted but not yet visible in a sample count as pending, so a burst of openings cannot overshoot the cap. A pending entry clears once a sample confirms it, or after 60 seconds.
  3. Enforcing the cap

    A NEW editing session is admitted when current + pending < limit. limit is 50 on the community edition; with a valid enterprise token, limit is exactly the token's conns, even when that number is below 50. O3O_GATE_CONNECTION_CAP can only lower the cap. When someone leaves, the slot is usable from the next sample.
  4. 5 minutes sliding peak

    peak_5m is the highest current over the last 300 seconds. It is used for REPORTING and reconciliation, never for refusing sessions.
  5. 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#

Situationv1 behaviour
Sessions already openUntouched, even when the cap is lowered (token expired or replaced).
Saving, automatic or manualNever blocked.
Reconnecting within 120 secondsAdmitted.
New read-only sessionAlways admitted, not counted.
New editing session through NextcloudThe 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.
JSONLimit message (community edition)
{
  "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"
  }
}
Coming soon

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#

GET/o3o/status

Gate, edition, license and connection status. No authentication; contains no secrets, file names or user names.

Auth: noneCommunityEnterprise
200Community edition example on a DEV machine.
{
  "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.limitintegerrequired
    Effective cap = the smaller of the plan cap and O3O_GATE_CONNECTION_CAP.
  • connections.currentintegerrequired
    Editing views in the latest sample.
  • connections.pendingintegerrequired
    Editing sessions admitted but not yet sampled.
  • connections.peak_5mintegerrequired
    Peak of current within the sliding window.
  • connections.readonlyintegerrequired
    Read-only views (not counted).
  • connections.limit_reachedbooleanrequired
    current + pending >= limit.
  • connections.sourcestringrequired
    adminws when counting works; none when no fresh sample exists.
  • connections.enforcingbooleanrequired
    false means the gate is failing open.
  • upstream.coolwsdstringrequired
    ok, down (unreachable), auth_failed (wrong admin credentials).
  • dev_imagebooleanrequired
    true when the editing server runs the DEV image, for internal testing only.
Watching the connection count
# 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.

BashCopy the usage log out
# 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,community
Coming soon

30-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.