WOPI
WOPI capabilities have two layers:
- launch layer: an authenticated user creates a WOPI launch session for a file
- protocol layer: the Office / WOPI host calls back to
/api/v1/wopi/files/{id}and/contents
Launch endpoints
Section titled “Launch endpoints”The following paths are relative to /api/v1 and require authentication.
| Method | Path | Description |
|---|---|---|
POST | /files/{id}/wopi/open | Create a WOPI launch session for a personal file |
POST | /teams/{team_id}/files/{id}/wopi/open | Create a WOPI launch session for a team file |
Request body:
{ "app_key": "custom.onlyoffice"}Response is WopiLaunchSession inside the unified wrapper:
{ "code": "success", "msg": "", "data": { "access_token": "...", "access_token_ttl": 1775995200000, "action_url": "https://office.example.com/hosting/wopi/word/edit?WOPISrc=https%3A%2F%2Fdrive.example.com%2Fapi%2Fv1%2Fwopi%2Ffiles%2F1", "form_fields": {}, "mode": "iframe" }}Semantics:
app_keymust match an enabled/public/preview-appsentry withprovider = "wopi"public_site_urlmust be configured because the server must generate an absoluteWOPISrc- with multiple public origins, the current request host is matched exactly first, then the first configured origin is used as fallback
- if
config.action_urlis configured, the server expands or appendsWOPISrcdirectly - otherwise, if
config.discovery_urlis configured, discovery XML is fetched and an action URL is selected by extension, MIME type, then wildcard access_token_ttlis a Unix millisecond expiry timestamp, not a TTL in seconds- team launch routes still callback to the shared
/api/v1/wopi/files/{id}; the team scope is encoded in the access token
Protocol callback endpoints
Section titled “Protocol callback endpoints”These paths are also relative to /api/v1, but they are WOPI host callbacks, not ordinary frontend JSON APIs.
Successful responses are raw WOPI JSON or file streams. Errors reuse the unified ApiResponse JSON error shape.
| Method | Path | Description |
|---|---|---|
GET | /wopi/files/{id}?access_token=... | CheckFileInfo |
POST | /wopi/files/{id}?access_token=... | LOCK / UNLOCK / REFRESH_LOCK / GET_LOCK / PUT_RELATIVE / RENAME_FILE / PUT_USER_INFO |
GET | /wopi/files/{id}/contents?access_token=... | Read file content |
POST | /wopi/files/{id}/contents?access_token=... | Overwrite content with X-WOPI-Override: PUT |
GET /wopi/files/{id}
Section titled “GET /wopi/files/{id}”Returns raw WOPI CheckFileInfo JSON, not ApiResponse.
Current fields include:
BaseFileNameFileNameMaxLengthOwnerIdSizeUserIdUserCanNotWriteRelativeUserCanRenameUserInfoUserCanWriteReadOnlySupportsGetLockSupportsLocksSupportsExtendedLockLengthSupportsRenameSupportsUserInfoSupportsUpdateVersion
Current implementation sets write/update/lock/rename capabilities according to the existing file permissions and persisted launch session. If the user profile contains wopi_user_info, it is passed through to UserInfo.
POST /wopi/files/{id}
Section titled “POST /wopi/files/{id}”Dispatches by X-WOPI-Override:
LOCKUNLOCKREFRESH_LOCKGET_LOCKPUT_RELATIVERENAME_FILEPUT_USER_INFO
LOCK + X-WOPI-OldLock performs WOPI UnlockAndRelock. Unsupported overrides return 501 Not Implemented.
Lock operations
Section titled “Lock operations”LOCK / UNLOCK / REFRESH_LOCK / GET_LOCK use:
X-WOPI-Lock
UnlockAndRelock additionally uses:
X-WOPI-OldLock
Conflicts return 409 with X-WOPI-LockFailureReason and, when known, X-WOPI-Lock.
Current implementation notes:
- loading WOPI lock state prunes expired lock rows for the file before evaluating the request
- repeating
LOCKwith the same opaque lock value refreshes the lock timeout LOCKwithX-WOPI-OldLockperforms an atomic unlock-and-relock when the old value matches- non-WOPI locks and multiple simultaneous active lock rows are treated as conflicts outside WOPI, usually with an empty
X-WOPI-Lock GET_LOCKreturns200with the currentX-WOPI-Lock; when no active lock exists, the value is emptyUNLOCK/REFRESH_LOCKwithout an active lock return409
PUT_RELATIVE
Section titled “PUT_RELATIVE”Creates or overwrites a sibling file. Required headers:
- exactly one of
X-WOPI-SuggestedTargetorX-WOPI-RelativeTarget - optional
X-WOPI-OverwriteRelativeTarget - optional
X-WOPI-Size
Success returns raw WOPI JSON with Name and Url. Conflicts may include X-WOPI-ValidRelativeTarget.
RENAME_FILE
Section titled “RENAME_FILE”Driven by:
X-WOPI-RequestedName- optional
X-WOPI-Lock
The server validates and normalizes the requested name. Name conflicts are resolved by returning an available name rather than blindly failing. Invalid names return 400 with X-WOPI-InvalidFileNameError.
PUT_USER_INFO
Section titled “PUT_USER_INFO”Persists a WOPI host user-state string to the user profile. Constraints:
- body must be valid UTF-8
- body must be ASCII
- maximum length is
1024bytes
GET /wopi/files/{id}/contents
Section titled “GET /wopi/files/{id}/contents”GET /wopi/files/{id}/contents returns the raw file stream. It behaves like normal download: inline by default, supports If-None-Match, and supports X-WOPI-MaxExpectedSize.
POST /wopi/files/{id}/contents
Section titled “POST /wopi/files/{id}/contents”POST /wopi/files/{id}/contents currently supports only X-WOPI-Override: PUT. It overwrites through the normal file-content update path, writes version history, updates ETag/version information, and returns X-WOPI-ItemVersion. If an active WOPI lock exists, the caller must provide the matching X-WOPI-Lock.
Security boundary
Section titled “Security boundary”The protocol layer validates:
- access token exists and is not expired
- file ID, user session version, and team scope in the token match
- disabled users, revoked sessions, disabled WOPI apps, or removed WOPI apps invalidate persisted sessions
- when trusted origins can be derived from app config,
Origin/Refereris checked
Missing Origin and Referer are allowed. Malformed headers return 400. Untrusted origins return an unauthorized protocol response.