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}}.
/o3o/api.jsThe embed layer JavaScript library.
/o3o/embed/sessionCreate an embed session.
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
documentobjectrequiredSee the configuration reference. May live insidetoken.editorobjectrequiredSee the configuration reference. May live insidetoken.uiobjectoptionalSee the configuration reference.tokenstringoptionalHS256 JWT; when valid it replaces the three fields above.
{
"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_idstringrequiredses_+ 24 hex.mode, requested_modestringrequiredActual and requested mode.limited, limit, messageboolean, object | null, string | nullrequiredCap information;nullwhen not downgraded.editor_urlstringrequiredRelative editor page path, to be joined with the O3O server origin.formobjectrequiredForm POST fields sent into the iframe:access_token,access_token_ttl,ui_defaults. NEVER put them in a URL.versionintegerrequiredCurrent document version.expires_atstringrequiredExpiry ofaccess_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.
/o3o/embed/session/{session_id}Session and document 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"
}
]
}/o3o/embed/session/{session_id}/modeSwitch mode; body {"mode": "view"} or {"mode": "edit"}. Returns the 201 schema, keeping session_id and access_token; switching to edit applies the cap.
/o3o/embed/session/{session_id}Marks the session closed, returns 204. Does not force the editor closed or delete the document.
# 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);/o3o/embed/files/{doc_id}?v={n}&exp={unix}&sig={hex}Download version n of the document (Content-Disposition: attachment).
Query parameters
vintegerrequiredVersion number (0 is the original).expnumber, Unix secondsrequiredLink expiry. Past it:410 link_expired.sighexrequiredSignature made by the gate. Wrong:403 invalid_signature. Version cleaned up:410 version_gone.
# 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)#
| Request | Purpose |
|---|---|
GET /o3o/demo | Test page using api.js: opens sample.docx, logs every event, has a button per method. |
GET /o3o/demo/sample.docx | Vietnamese sample file shipped in the image. |
POST /o3o/demo/callback | Receives callbacks, checks signatures, keeps the last 20 in memory. |
GET /o3o/demo/callbacks | List of received callbacks, as JSON. |