Skip to content

Editor embedding

Embed session API

The REST endpoints api.js uses: create a session, read its state, switch mode, close it, download signed versions, CORS and demo endpoints.

On this page

api.js calls the endpoints below for you. Call them directly for diagnostics, to create sessions from a server, or to write an embed layer for another UI framework. Every path is under /o3o/; every response carries an X-O3O-Request-Id header; errors look like {error: {code, message, detail, request_id}}.

GET/o3o/api.js

The embed layer JavaScript library.

Auth: noneCommunityEnterprise
POST/o3o/embed/session

Create an embed session.

Auth: JWT in the body (required when the server sets a secret)CommunityEnterprise

The body is the config from the configuration reference without events, Content-Type: application/json. The gate checks origin, token and config; downloads the file if the key is not open yet; applies the cap when mode = "edit"; reads the editor path from discovery; then answers 201.

Request body

  • documentobjectrequired
    See the configuration reference. May live inside token.
  • editorobjectrequired
    See the configuration reference. May live inside token.
  • uiobjectoptional
    See the configuration reference.
  • tokenstringoptional
    HS256 JWT; when valid it replaces the three fields above.
201Session created
{
  "session_id": "ses_5b1d0c2a9f3e4d6a8b7c0d1e",
  "key": "hopdong-42-v7",
  "doc_id": "doc_9835f61009fc659dfedab8696f2d2665",
  "mode": "edit",
  "requested_mode": "edit",
  "limited": false,
  "limit": null,
  "message": null,
  "editor_url": "/browser/825c9caa93/cool.html?WOPISrc=http%3A%2F%2Fo3o-gate%3A8070%2Fo3o%2Fwopi%2Ffiles%2Fdoc_9835f61009fc659dfedab8696f2d2665&lang=vi&closebutton=1",
  "form": {
    "access_token": "kq3…",
    "access_token_ttl": 1790043200000,
    "ui_defaults": "UIMode=notebookbar"
  },
  "version": 0,
  "expires_at": "2026-09-21T22:00:00Z"
}

201 response

  • session_idstringrequired
    ses_ + 24 hex.
  • mode, requested_modestringrequired
    Actual and requested mode.
  • limited, limit, messageboolean, object | null, string | nullrequired
    Cap information; null when not downgraded.
  • editor_urlstringrequired
    Relative editor page path, to be joined with the O3O server origin.
  • formobjectrequired
    Form POST fields sent into the iframe: access_token, access_token_ttl, ui_defaults. NEVER put them in a URL.
  • versionintegerrequired
    Current document version.
  • expires_atstringrequired
    Expiry of access_token.

Embedding without api.js: create <iframe name="…" allow="clipboard-read; clipboard-write; fullscreen">, create <form method="post" target="…"> with action = server origin + editor_url and one hidden input per form key, submit it, then remove it. Only accept postMessage whose origin is the server origin.

GET/o3o/embed/session/{session_id}

Session and document state.

Auth: Bearer access_tokenCommunityEnterprise
200Session state
{
  "session_id": "ses_5b1d0c2a9f3e4d6a8b7c0d1e",
  "key": "hopdong-42-v7",
  "doc_id": "doc_9835f61009fc659dfedab8696f2d2665",
  "mode": "edit",
  "state": "active",
  "version": 3,
  "modified": false,
  "saved_at": "2026-09-21T10:05:00Z",
  "size": 48890,
  "sha256": "b1946ac92492d2347c6235b4d2611184…",
  "download_url": "http://localhost:8080/o3o/embed/files/doc_9835f61009fc659dfedab8696f2d2665?v=3&exp=1790086400&sig=5f1c…",
  "users": [
    {
      "id": "u-1001",
      "name": "Nguyễn Văn A"
    }
  ]
}
POST/o3o/embed/session/{session_id}/mode

Switch mode; body {"mode": "view"} or {"mode": "edit"}. Returns the 201 schema, keeping session_id and access_token; switching to edit applies the cap.

Auth: Bearer access_tokenCommunityEnterprise
DELETE/o3o/embed/session/{session_id}

Marks the session closed, returns 204. Does not force the editor closed or delete the document.

Auth: Bearer access_tokenCommunityEnterprise
Try with curl and Node.js
# Create a read-only session for diagnostics (view mode holds no connection slot)
curl -s -X POST http://localhost:8080/o3o/embed/session \
  -H "Content-Type: application/json" \
  -d '{"document": {"url": "http://localhost:8080/o3o/demo/sample.docx", "title": "sample.docx", "fileType": "docx", "key": "diag-1"},
       "editor": {"mode": "view", "lang": "vi", "user": {"id": "ops-1", "name": "Ops"}}}' > session.json

SESSION=$(python -c "import json; print(json.load(open('session.json'))['session_id'])")
TOKEN=$(python -c "import json; print(json.load(open('session.json'))['form']['access_token'])")

# Session state
curl -s http://localhost:8080/o3o/embed/session/$SESSION -H "Authorization: Bearer $TOKEN"

# Switch mode
curl -s -X POST http://localhost:8080/o3o/embed/session/$SESSION/mode \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{"mode": "edit"}'

# Close the session (204)
curl -s -o /dev/null -w "%{http_code}\n" -X DELETE http://localhost:8080/o3o/embed/session/$SESSION -H "Authorization: Bearer $TOKEN"
// Called from a Node.js 18+ server (no Origin header, PostMessageOrigin = O3O_PUBLIC_URL)
const res = await fetch("http://localhost:8080/o3o/embed/session", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ token: signedToken })   // token only: the gate uses document, editor, ui from the token
});
const session = await res.json();
if (!res.ok) throw new Error(session.error.code + ": " + session.error.message);
console.log(session.session_id, session.mode, session.limited);
GET/o3o/embed/files/{doc_id}?v={n}&exp={unix}&sig={hex}

Download version n of the document (Content-Disposition: attachment).

Auth: Signature in the URLCommunityEnterprise

Query parameters

  • vintegerrequired
    Version number (0 is the original).
  • expnumber, Unix secondsrequired
    Link expiry. Past it: 410 link_expired.
  • sighexrequired
    Signature made by the gate. Wrong: 403 invalid_signature. Version cleaned up: 410 version_gone.
BashDownload a version
# download_url from GET session, or url from the callback / onSaved
curl -f -o v3.docx "http://localhost:8080/o3o/embed/files/doc_9835f61009fc659dfedab8696f2d2665?v=3&exp=1790086400&sig=<sig>"

CORS#

The gate returns Access-Control-Allow-Origin equal to the request Origin when that origin is in O3O_EMBED_ALLOWED_ORIGINS (or the list is *); OPTIONS answers Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS, Access-Control-Allow-Headers: Content-Type, Authorization, Access-Control-Max-Age: 600. Requests without Origin (servers, curl) are accepted.

Demo endpoints (DEV only)#

RequestPurpose
GET /o3o/demoTest page using api.js: opens sample.docx, logs every event, has a button per method.
GET /o3o/demo/sample.docxVietnamese sample file shipped in the image.
POST /o3o/demo/callbackReceives callbacks, checks signatures, keeps the last 20 in memory.
GET /o3o/demo/callbacksList of received callbacks, as JSON.