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