Skip to content

Editor embedding

Errors and connection limits

Embed error codes, SSRF rules, browser-side behaviour when the connection cap is reached, and time and size limits.

On this page

Every error reaches your page through onError as {code, message, detail}: code is stable for branching, message is a Vietnamese sentence you can show. Reaching the connection cap is not an error and goes through onLimitReached.

Server-side errors#

HTTPcodeWhenWhat to do
400invalid_configMissing or wrong field; detail.errors = [{path, message}].Fix the config; do not retry.
401token_requiredThe server requires a JWT but the config has none.Sign the config on your server.
401invalid_tokenBad signature, not HS256, missing exp, exp more than 24 hours away.Check the secret and algorithm.
401token_expiredPast exp.Get a new token and reopen.
401embed_auth_not_configuredNo secret on the server and unsigned mode is off.Admin sets O3O_EMBED_JWT_SECRET.
401invalid_session_tokenAuthorization does not match the session.Create a new session.
403origin_not_allowedOrigin is not in O3O_EMBED_ALLOWED_ORIGINS.Add the page origin.
403invalid_signatureWrong signature on a download URL.Use the URL exactly as returned.
404not_foundSession or document does not exist; or embedding is disabled.Check O3O_EMBED_ENABLED.
410link_expired, version_goneDownload URL expired; version was cleaned up.Download sooner, within the retention time.
413file_too_largeAbove O3O_EMBED_MAX_FILE_MB (100 MB by default).Do not retry.
415unsupported_file_typefileType outside the list or not openable by the editor server.Do not retry.
422url_not_allowedSSRF rule violation.Use a public URL or configure O3O_FETCH_ALLOW_HOSTS.
422download_failedCould not download document.url.Check the file server; retry is possible.
503editor_unavailableThe gate cannot read the editor server's discovery.Retry later; alert the admin.

Browser-only errors#

codeWhen
network_errorThe O3O server cannot be reached.
load_failedThe editor frame reports a load failure, or the document does not show within 120 seconds.
save_failedThe editor frame reports a failed save; detail holds the raw message.
save_timeoutsave() took more than 30 seconds.

SSRF rules#

Applied to every URL the server calls itself (document.url, callbackUrl): http/https only, no user:pass@, at most 2048 characters; every resolved address must be public (private, loopback, link-local and multicast ranges are blocked), and the gate connects straight to the checked IP address without resolving again (DNS rebinding protection); redirects are not followed automatically (downloads handle up to 3 and re-check each hop, callbacks treat a redirect as a failure); only status 200 counts as a successful download. The one deliberate exception: O3O_FETCH_ALLOW_HOSTS.

When the connection cap is reached#

  • The page asks for mode: "edit" while current + pending >= limit: the gate still creates the session, in view, returns limited: true, and api.js fires onLimitReached.
  • api.js inserts a light-yellow banner with a dismiss button above the frame, showing the server's message verbatim. Turn it off with ui.limitBanner: false.
  • Open editing sessions are never cut and saves are never blocked.
  • Reopening the document you just left within 120 seconds is allowed even at the cap (brief network loss, page reload).
  • The community edition cap is 50; the enterprise edition follows the connections purchased. See limits and plans.
201Session response when the cap is reached
{
  "session_id": "ses_0c4e9a1b2d3f4a5b6c7d8e9f",
  "key": "contract-42-v7",
  "doc_id": "doc_9835f61009fc659dfedab8696f2d2665",
  "mode": "view",
  "requested_mode": "edit",
  "limited": true,
  "limit": {
    "limit": 50,
    "current": 50,
    "edition": "community"
  },
  "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.",
  "editor_url": "/browser/825c9caa93/cool.html?WOPISrc=http%3A%2F%2Fo3o-gate%3A8070%2Fo3o%2Fwopi%2Ffiles%2Fdoc_9835f61009fc659dfedab8696f2d2665&lang=vi&permission=readonly",
  "form": {
    "access_token": "kq3…",
    "access_token_ttl": 1790043200000,
    "ui_defaults": "UIMode=notebookbar"
  },
  "version": 0,
  "expires_at": "2026-09-21T22:00:00Z"
}
HTMLHandle errors and the cap your own way
<div id="banner" hidden style="background: #fff4d6; padding: 8px 12px"></div>
<div id="o3o-editor" style="height: 90vh"></div>
<script src="http://localhost:8080/o3o/api.js"></script>
<script>
  let editor;
  let retries = 0;

  async function openEditor() {
    // Your server returns a signed config (see Signing the config with JWT)
    const config = await (await fetch("/o3o/config?doc=42")).json();
    editor = new O3O.Editor("o3o-editor", {
      ...config,
      ui: { ...(config.ui || {}), limitBanner: false },    // draw our own banner
      events: { onDocumentLoaded: () => { retries = 0; }, onError, onLimitReached }
    });
  }

  function onError(e) {
    switch (e.code) {
      case "token_expired":        // token expired: fetch a new config and reopen
      case "network_error":        // O3O unreachable: retry with growing delay
      case "load_failed":
        if (retries++ < 3) {
          editor.destroy();
          setTimeout(openEditor, 2000 * retries);
          return;
        }
        break;
      case "save_failed":
      case "save_timeout":         // the document is still open; ask the user to save again
        return showBanner("Could not save. Please save again.");
      case "url_not_allowed":
      case "download_failed":
      case "file_too_large":
      case "unsupported_file_type": // file or URL problem: retrying will not help
        return showBanner(e.message);
    }
    showBanner(e.message);
    console.error(e.code, e.detail);
  }

  function onLimitReached(e) {
    // Not an error: the document is open read-only
    showBanner(e.message, "Try editing again", () => editor.setReadOnly(false));
  }

  function showBanner(text, actionLabel, action) {
    const el = document.getElementById("banner");
    el.textContent = text;
    if (actionLabel) {
      const b = document.createElement("button");
      b.textContent = actionLabel;
      b.onclick = action;
      el.append(" ", b);
    }
    el.hidden = false;
  }

  openEditor();
</script>

Other limits#

LimitDefaultVariable
Embedded file size (download and save)100 MBO3O_EMBED_MAX_FILE_MB
Session access_token lifetime720 minutesO3O_EMBED_SESSION_TTL_MINUTES
Created session the editor never loadscancelled after 300 secondsO3O_EMBED_PENDING_TTL_SECONDS
Working copy kept after closing24 hoursO3O_EMBED_RETAIN_HOURS
Waiting for the document to show120 seconds, then load_failedfixed
save()30 seconds, then save_timeoutfixed
close() waiting for a save10 secondsfixed

Embedding page on another origin#

A page on localhost (any port) can embed without configuration. A page on another origin needs two things: add the origin to O3O_EMBED_ALLOWED_ORIGINS (CORS for /o3o/embed/*), and set O3O_ONLINE_FRAME_ANCESTORS (a single value without spaces, e.g. https://app.example.com or https://*.example.com) so the browser allows framing. Several unrelated origins in O3O_ONLINE_FRAME_ANCESTORS: coming soon.