Skip to main content

HTTP Storage Protocol

HTTP Storage Protocol v1 defines requests and responses between the Office HTTP Storage adapter and an HTTP Storage Provider that operates its own storage service. The Provider implements the endpoints below, and Office sends requests to them. See HTTP Storage for connection instructions and examples.

This document defines the methods and paths, request headers and bodies, response fields, and status codes for all nine endpoints. Common authentication, path, and size rules apply to every endpoint. Additional conditions appear in each operation's section.

URLs and paths

{PROVIDER_BASE_URL}/tfo-storage/v1/{ENCODED_DOCUMENT_PATH}/{OPERATION}

The Provider base URL is an HTTP or HTTPS address and may include a path prefix. User information, query strings, fragments, and . or .. path segments are not allowed. Remove any trailing / before appending the protocol path.

Document paths are relative to the Provider root. Percent-encode each UTF-8 path segment separately and join them with /. The root has an empty document path, as in /tfo-storage/v1/list. Do not insert empty intermediate segments or extra / characters.

ItemExample
Provider base URLhttps://storage.example.com/office
Document pathcontracts/Proposal 2026.docx
Document information requestGET /office/tfo-storage/v1/contracts/Proposal%202026.docx/info
Root listing requestGET /office/tfo-storage/v1/list

The Provider must resolve decoded paths only within its storage root. Reject . and .., / or \ inside path segments, control characters, invalid UTF-8, and symbolic links that escape the root. The protocol does not use query parameters, cookies, redirects, or forwarding of arbitrary authentication headers. Proxies must preserve the raw encoded path used for JWT verification.

Common request headers

HeaderScopeValue
X-TFO-Storage-AdapterRequired on every requestThe registered adapter name: 1–128 alphanumeric, ., _, or - characters, beginning with an alphanumeric character
X-TFO-Storage-Request-JWTRequired on every requestThe raw JWT signed according to the rules below, without a Bearer prefix
Content-Typeput, lock, unlock, mkdir, renameapplication/octet-stream for put; application/json for JSON operations
Content-LengthRequired on requests with a bodyA single decimal integer giving the exact body length in bytes

Requests for info, list, get, and delete have no body or Content-Type. Sign them with body length 0 and the SHA-256 digest of an empty body. Do not wrap bodies in multipart or base64, or use chunked uploads or compression. For JSON bodies, sign the actual UTF-8 serialized bytes.

JWT signing and verification

Before reading or modifying a document, the Provider must verify that the request was signed with the connected Office adapter's secret and that its method, path, and body were not changed in transit. Office signs a JWT containing request information with the shared secret. The Provider verifies the signature with the same secret and compares it with the actual request. It also checks the short validity period and unique request ID to reject replayed requests.

Signatures protect request integrity; they do not encrypt document contents. Use HTTPS to protect data in transit, and separately enforce user and tenant document access permissions in the Provider.

Office and the Provider share a random secret of at least 32 UTF-8 bytes per adapter. The HMAC key is the UTF-8 byte representation of this string. Never send the secret in request URLs or bodies.

The JWT uses compact JWS format. Serialize the header and payload separately as UTF-8 JSON and Base64URL-encode them without padding. Join them with ., apply HMAC-SHA256 to those bytes, and append the signature using the same Base64URL encoding.

{"alg":"HS256","typ":"tfo-storage-request+jwt"}

The following payload example signs a request that saves the five-byte body hello. Generate fresh iat, exp, and jti values for every call.

{
"iss": "thinkfree-office",
"aud": "tfo-http-storage-provider",
"iat": 1789344000,
"exp": 1789344060,
"jti": "26bc9b5e-9dab-4a78-95d8-b1ae34a5d9eb",
"request": {
"adapter": "customer-storage-a",
"method": "PUT",
"path": "/tfo-storage/v1/contracts/hello.txt/put",
"content_type": "application/octet-stream",
"content_length": 5,
"content_sha256": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
"office_connection_id": "example-connection",
"arguments": {"save_type": "save"},
"client_metadata": {"project": "example-project"}
}
}
FieldTypeRequiredValidation rule
Header algstringYesExactly HS256
Header typstringYesExactly tfo-storage-request+jwt
issstringYesExactly thinkfree-office
audstring or single-element arrayYesThe sole audience is tfo-http-storage-provider
iatintegerYesIssued-at Unix time in seconds, not later than the current time
expintegerYesExpiration Unix time in seconds, later than the current time, with 0 < exp - iat <= 60
jtistringYesA unique ID for each request. Office generates a UUID; the public examples accept 1–64 characters
requestobjectYesContains the signed request fields below
request.adapterstringYesMatches both the actual adapter header and the connection name configured in the Provider
request.methodstringYesMatches the actual uppercase HTTP method
request.pathstringYesMatches the actual raw encoded path, including the Provider base path, without scheme, host, or query
request.content_typestringWhen the header is presentExactly matches the actual Content-Type
request.content_lengthintegerYesThe actual number of body bytes received, or 0 when there is no body
request.content_sha256stringYesSHA-256 of the actual body bytes, as 64 lowercase hexadecimal characters
request.office_connection_idstringNoOpaque Office connection context; not proof of user authentication
request.argumentsobjectNoOperation context such as save_type; does not grant access
request.client_metadataobjectNoCaller-provided JSON. Only transport integrity is guaranteed; do not trust it as an Office-verified user identity

The SHA-256 digest of an empty body is:

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

The complete JWT must not exceed 5,120 bytes. client_metadata is limited to 2,048 bytes of UTF-8 JSON. Object keys must contain 1–64 characters and must not be blank; string values are limited to 512 characters. The maximum depth is 8, counting the root object as depth 0. Do not include secrets or document contents.

Verification order

  1. Check the actual HTTP method, raw path, presence of a query, media type, and fixed length. Reject oversized bodies before reading or buffering them.
  2. Read allowed bodies into bounded memory or a temporary file while calculating their actual length and SHA-256 digest. Do not replace the target document before the upload completes.
  3. Look up the configured secret using the adapter header, then verify the JWT signature, header, issuer, audience, and times.
  4. Compare the signed adapter, method, raw path, body type, length, and hash with the actual request.
  5. Record jti atomically and reject reuse until exp. Multiple Provider instances must share the same replay-protection store.
  6. Check document access permissions and perform the operation, or return the exact unsupported response for an optional operation.

Reject invalid signatures and reused JWTs before accessing storage or reporting unsupported operations. Signature verification does not replace the Provider's user and tenant document authorization checks. Synchronize server clocks and sign retries with fresh iat, exp, and jti values. jti is not an idempotency key that returns the successful result of a duplicate operation. If a response is lost, check the storage state before retrying according to the operation's semantics.

Response framing and size limits

Successful info, list, get, and put responses must have exactly one decimal Content-Length header matching the number of bytes actually sent. Transfer-Encoding and Content-Encoding are not allowed. A charset parameter in the media type is allowed, but JSON bodies must be UTF-8.

TargetContent-TypeLimit
Successful info, list, and put responsesapplication/json5 MiB each (5,242,880 bytes)
Successful get responseapplication/octet-stream300 MiB (314,572,800 bytes)
put requestapplication/octet-stream300 MiB (314,572,800 bytes)
File size in metadataJSON integer0–314,572,800
entries in listJSON arrayAt most 10,000 entries, with the complete JSON also limited to 5 MiB

Serialize JSON once, calculate its byte length, and send those exact bytes. For get, obtain the original file size from storage before sending response headers, then stream exactly that many bytes. Do not load an entire document into memory just to determine its size.

A Provider may impose a lower document limit but must not exceed 300 MiB. Check the limit before processing file metadata, sending get, or buffering put. Limits are inclusive: JSON of 5,242,880 bytes and documents of 314,572,800 bytes are allowed, while 5,242,881 and 314,572,801 bytes respectively must be rejected.

Missing, duplicate, negative, or fractional lengths; compressed or chunked responses; incorrect media types; early EOF; and actual length mismatches fail the current operation. The Provider must remain able to process subsequent requests after Office rejects an invalid response. Authenticated 501 responses and ordinary errors follow the separate rules below.

Success statuses and bodies

OperationNormal responseBody contract
info, list200 OKA JSON object is required; do not substitute an empty body or 204
get200 OKThe complete file bytes; return an empty file with Content-Length: 0
put200 OKA JSON object with docId is required. See PUT response
lock, unlock, mkdir, rename, delete204 No ContentNo body

Office classifies 2xx statuses as successful, but info, list, get, and put must also pass the media type, fixed-length, and body checks above. lock, unlock, mkdir, rename, and delete also accept other 2xx statuses and require no specific response JSON fields. Never send a body with 204. If these five operations return result text in a 200 response, use text/plain; charset=utf-8 and keep it within the 10 MiB limit Office reads. Do not return document contents in that result text.

Metadata fields

info returns one entry object; list returns one object containing an entries array. Do not add fields other than those below to entry objects.

FieldTypeRequiredMeaning and limits
pathstringYesDecoded path relative to the root, at most 4,096 characters. The root uses an empty string
namestringYesA 1–255 character name matching the final path segment. The root uses a fixed display name
typestringYesfile or directory
sizeintegerYesFile length in bytes, 0–314,572,800. Must be 0 for directories
readablebooleanYesWhether the document can be read
writablebooleanYesWhether the document can be written
lockedbooleanYesCurrent lock state
lockerstring or nullNoLock owner, at most 255 characters
createdAtstring or nullNoRFC 3339 creation time, at most 64 characters
modifiedAtstring or nullNoRFC 3339 modification time, at most 64 characters
revisionstring or nullNoStorage revision or ETag-equivalent value, at most 1,024 characters

info.path must match the requested document path. list must contain only the requested directory's immediate children, with no duplicate paths. There are no pagination parameters. Invalid types, timestamps, names, and sizes; unknown fields; duplicate paths; and entries from inside subdirectories are rejected.

Required fields must not be null. Optional fields may be omitted or returned as null. path is decoded, not URL-encoded. createdAt and modifiedAt include a time zone, as in 2026-09-14T00:00:00Z. readable and writable describe an entry's accessibility; continue to enforce authorization on actual requests. revision describes the storage version and is not automatically used as a conditional request header or lock token in this protocol.

For machine validation, use the entry schema, info response schema, and list response schema. Also check the path relationships and runtime limits above in addition to schema validation.

Requests and responses by operation

path identifies the target entry, and parent identifies the parent for a new directory. Both are relative paths under the Provider root. Encode each segment according to the URL rules, rather than encoding the entire path. Every request requires the common authentication headers, X-TFO-Storage-Adapter and X-TFO-Storage-Request-JWT, and has no query parameters.

JSON requests are objects containing only the single field specified below. Required fields cannot be omitted, null, or another type, and extra fields are not allowed. The request Content-Type must be exactly application/json, and Content-Length is the length of the UTF-8 serialized bytes. The public examples limit JSON requests to 16 KiB, separately from the 5 MiB response limit.

Each operation's error table describes its specific conditions. The common errors for paths, authentication, permissions, body formats, and storage failures also apply to every endpoint. Office does not require every Provider to use an identical ordinary error body. Conflict and filesystem policies specific to the public examples are identified separately.

OperationMethodPath suffixImplementation
infoGET/{path}/infoRequired
listGET/{path}/listOptional
getGET/{path}/getRequired
putPUT/{path}/putOptional
lockPOST/{path}/lockOptional together with unlock
unlockPOST/{path}/unlockOptional together with lock
mkdirPOST/{parent}/mkdirOptional
renamePOST/{path}/renameOptional
deleteDELETE/{path}/deleteOptional

The examples below use base URL https://storage.example.com. Replace <SIGNED_REQUEST_JWT> in every request with a fresh JWT signing that request's method, path, and body. Example bodies exclude the final newline in the code block; the displayed Content-Length values reflect this. The hello body of hello.txt is a five-byte example showing raw file bytes.

info - Retrieve document information

This required endpoint retrieves metadata for a document or directory. It does not return document contents.

Request

GET /tfo-storage/v1/{path}/info

GET /tfo-storage/v1/contracts/hello.txt/info HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
ItemFormatContract
pathPath · stringRelative file or directory path. Use /tfo-storage/v1/info for the root
Authentication headersRequired · stringAdapter name and JWT for this GET request. See Common headers
Request bodyNoneOmit Content-Type. Set the JWT body length to 0 and hash to the SHA-256 digest of an empty body

Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 229

{"path":"contracts/hello.txt","name":"hello.txt","type":"file","size":5,"readable":true,"writable":true,"locked":false,"locker":null,"createdAt":"2026-09-14T00:00:00Z","modifiedAt":"2026-09-14T00:10:00Z","revision":"revision-17"}
ItemFormatContract
Status200 OKTarget entry metadata retrieved successfully
Content-TypeRequired headerapplication/json
Content-LengthRequired headerExact response JSON length in bytes, at most 5,242,880
BodyobjectOne entry object containing the metadata fields directly, without an entry or data wrapper

Required fields are path, name, type, size, readable, writable, and locked. Optional fields are locker, createdAt, modifiedAt, and revision; they may be omitted or null. Field types, lengths, and allowed values follow the common entry schema. The response path must match the decoded request path. For the root, use path: "", type: "directory", and size: 0.

StatusConditions and handling
404 Not FoundThe target does not exist. Office treats the entry as absent and does not require fabricated metadata
413 Payload Too LargeFile size or response JSON exceeds its limit
501 Not ImplementedNot accepted as an unsupported capability because this endpoint is required

list - List child entries

This optional endpoint lists the immediate files and directories within a directory. It provides neither recursive listing nor pagination.

Request

GET /tfo-storage/v1/{path}/list

GET /tfo-storage/v1/contracts/list HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
ItemFormatContract
pathPath · stringDirectory to list. Use /tfo-storage/v1/list for the root
Authentication headersRequired · stringAdapter name and JWT for this GET request. See Common headers
Request bodyNoneOmit Content-Type. Set the JWT body length to 0 and hash to the SHA-256 digest of an empty body

Response

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 243

{"entries":[{"path":"contracts/hello.txt","name":"hello.txt","type":"file","size":5,"readable":true,"writable":true,"locked":false,"locker":null,"createdAt":"2026-09-14T00:00:00Z","modifiedAt":"2026-09-14T00:10:00Z","revision":"revision-17"}]}
ItemFormatContract
Status200 OKDirectory listed successfully, including an empty directory
Content-TypeRequired headerapplication/json
Content-LengthRequired headerExact length of the complete JSON in bytes, at most 5,242,880
entriesBody · array · required0–10,000 entry objects; the only field in the top-level object
entries[]objectSame required and optional fields as info. Each decoded path must be an immediate child of the listed directory, without duplicates

Entry paths are always relative to the root: return contracts in a root listing and contracts/hello.txt in a listing of contracts. Do not include the directory itself, its parent, or entries inside subdirectories. Ordering is unspecified. Do not send additional fields such as total, cursor, or hasMore.

Return 200 with {"entries":[]} for an empty directory. This body is 14 bytes long. If listing is not implemented, return 501 with LIST_NOT_SUPPORTED after authentication. An empty listing, a missing target (404), and an unsupported operation are distinct results.

StatusConditions and handling
404 Not FoundThe directory to list does not exist
409 ConflictThe target is not a directory (public examples)
413 Payload Too LargeEntry count, metadata size, or an included file size exceeds its limit. Do not return a partial listing as successful
501 Not ImplementedAfter authentication, {"code":"LIST_NOT_SUPPORTED"}. File listing is unavailable in Office

get - Download a document

This required endpoint returns the complete original file bytes. Office reads this content to open the document.

Request

GET /tfo-storage/v1/{path}/get

GET /tfo-storage/v1/contracts/hello.txt/get HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
ItemFormatContract
pathPath · stringFile to download. Use list to list a directory
Authentication headersRequired · stringAdapter name and JWT for this GET request. See Common headers
Request bodyNoneOmit Content-Type. Set the JWT body length to 0 and hash to the SHA-256 digest of an empty body

Range requests for partial downloads are not part of this contract. Return the file itself, rather than a download URL, JSON, or Base64.

Response

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 5

hello
ItemFormatContract
Status200 OKComplete file downloaded successfully
Content-TypeRequired headerapplication/octet-stream, regardless of file extension
Content-LengthRequired headerActual file length in bytes, 0–314,572,800
BodybinaryOriginal file bytes of exactly Content-Length; no JSON fields

Return an empty file with 200 and Content-Length: 0. Do not substitute compression, chunked transfer, redirects, or partial responses. Read a consistent version from storage so that changes during streaming cannot cause a mismatch with the declared length.

StatusConditions and handling
404 Not FoundThe file does not exist
409 ConflictThe target is not a file (public examples)
413 Payload Too LargeThe file exceeds the size limit. Reject it before sending success headers or file bytes
501 Not ImplementedNot accepted as an unsupported capability because this endpoint is required

put - Save a document

Receives the complete edited file as raw bytes. Partial updates and multipart uploads are not used. Buffer all bytes in temporary storage, verify authentication, length, hash, and access permissions, then atomically replace the target file. Preserve the existing file on failure.

Request

PUT /tfo-storage/v1/{path}/put

PUT /tfo-storage/v1/contracts/hello.txt/put HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
Content-Type: application/octet-stream
Content-Length: 5

hello
ItemFormatContract
pathPath · stringRelative path of the file to save. An empty path cannot overwrite the Provider root
Authentication headersRequired · stringAdapter name and JWT for this PUT request. See Common headers
Content-TypeRequired headerExactly application/octet-stream
Content-LengthRequired headerActual complete file length in bytes, 0–314,572,800
Request bodybinaryComplete file bytes, not a JSON object, Base64, or multipart data
request.arguments.save_typeJWT · string · optionalSave context supplied by Office, such as save. Do not send it as a separate body field or query parameter

save_type does not prove access permissions or lock ownership. This request defines no revision, If-Match, or lock-token fields for conditional saves. The Provider determines whether concurrent saves are accepted or rejected as conflicts. The public examples create or replace a file when its parent directory exists; they do not create parent directories automatically.

Response

After committing the file to storage, return the saved document's identifier as a JSON object.

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 29

{"docId":"saved-document-id"}
ItemFormatContract
Status200 OKOther 2xx statuses must pass body validation. 204 No Content is not allowed
Content-TypeRequired headerUTF-8 application/json
Content-LengthRequired headerExact JSON body byte count. The fixed-length rules apply
BodyJSON object · requiredContains only docId. Extra or duplicate fields, trailing JSON values, and trailing text are not allowed
docIdstring · required1–1,024 ASCII letters, digits, ., _, :, or -, starting with a letter or digit. true and false are reserved regardless of case

docId identifies the saved destination. Within an adapter, the same destination keeps the same ID when its contents change, and different destinations have different IDs. This applies to both regular saves and Save As. Do not substitute a revision or the original session's document ID.

Empty bodies, plain text IDs, XML, JSON strings, numbers, booleans (including true), null, and arrays are rejected. Do not trim IDs or convert other formats automatically. See the PUT response schema for the machine-readable contract. The Node.js, Spring Boot, and FastAPI examples hash the decoded root-relative path as UTF-8 with SHA-256 and return the 64-character lowercase hexadecimal result as the ID; production Providers can use their own document IDs that satisfy the same format.

StatusConditions and handling
400 Bad RequestEmpty document path, declared length differing from the actual body, or similar invalid input
404 Not FoundThe parent directory does not exist (public examples)
409 ConflictA directory occupies the file path, or the storage conflict policy rejects the save
413 Payload Too LargeThe document exceeds the protocol limit or a lower Provider limit
501 Not ImplementedAfter authentication, {"code":"PUT_NOT_SUPPORTED"}. Nothing is written to storage, and Office reports a save failure

On failure, preserve the existing file and clean up temporary data. Saving to the same destination again returns the same docId, even when contents or revisions change.

lock - Lock a document

This optional endpoint records a document's lock owner. A repeated request from the same owner succeeds; an existing lock held by another owner is a conflict. Implement it together with unlock.

Request

POST /tfo-storage/v1/{path}/lock

POST /tfo-storage/v1/contracts/hello.txt/lock HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
Content-Type: application/json
Content-Length: 20

{"owner":"editor-1"}
ItemFormatContract
pathPath · stringRelative path of the document to lock
Authentication headersRequired · stringAdapter name and JWT for this POST request. See Common headers
Content-TypeRequired headerExactly application/json
Content-LengthRequired headerUTF-8 byte length of the JSON body
ownerBody · string · requiredNonempty lock owner identifier; the only JSON field

Store owner unchanged and compare it for equality when processing unlock requests. Do not use it as a user authentication token. A value returned as locker in info or list must fit that field's 255-character limit. This request defines no lock expiration, lease duration, or renewal interval fields.

Response

HTTP/1.1 204 No Content
ItemFormatContract
Status204 No ContentA new lock was acquired or an existing lock for the same owner was confirmed. Other 2xx statuses also succeed
BodyNoneNo lock token or result JSON is required. Bodies of other 2xx responses contain no fields required to determine success
StatusConditions and handling
400 Bad RequestMissing owner, empty string, incorrect type, or extra JSON fields
404 Not FoundThe target to lock does not exist (public examples)
409 ConflictAnother owner holds the lock. Do not change the existing lock
501 Not ImplementedAfter authentication, {"code":"LOCK_NOT_SUPPORTED"}. Office treats this as successful without an actual lock

Generate a fresh JWT even when locking again with the same owner. If locking is unsupported, unlock must also be unsupported. Never disguise an actual lock conflict as LOCK_NOT_SUPPORTED.

unlock - Unlock a document

This optional endpoint releases the current owner's lock. It succeeds if no lock remains and returns a conflict if another owner's lock would be released. Implement it together with lock.

Request

POST /tfo-storage/v1/{path}/unlock

POST /tfo-storage/v1/contracts/hello.txt/unlock HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
Content-Type: application/json
Content-Length: 20

{"owner":"editor-1"}
ItemFormatContract
pathPath · stringRelative path of the document to unlock
Authentication headersRequired · stringAdapter name and JWT for this POST request. See Common headers
Content-TypeRequired headerExactly application/json
Content-LengthRequired headerUTF-8 byte length of the JSON body
ownerBody · string · requiredThe same owner identifier used to acquire the lock; the only JSON field

There is no force-unlock feature that omits owner or supplies a different value.

Response

HTTP/1.1 204 No Content
ItemFormatContract
Status204 No ContentThe same owner's lock was released, or no lock remains. Other 2xx statuses also succeed
BodyNoneNo unlock result JSON is required
StatusConditions and handling
400 Bad RequestMissing owner, empty string, incorrect type, or extra JSON fields
409 ConflictThe current lock owner differs. Preserve the existing lock
501 Not ImplementedAfter authentication, {"code":"UNLOCK_NOT_SUPPORTED"}. Office treats this as successful without an actual unlock operation

Use a fresh JWT even when retrying an already released lock. If unlocking is unsupported, lock must also be unsupported.

mkdir - Create a directory

This optional endpoint creates one directory directly under the specified parent. It does not create a multilevel path with intermediate directories.

Request

POST /tfo-storage/v1/{parent}/mkdir

POST /tfo-storage/v1/contracts/mkdir HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
Content-Type: application/json
Content-Length: 18

{"name":"archive"}
ItemFormatContract
parentPath · stringRelative path of an existing parent directory. Use /tfo-storage/v1/mkdir to create a directory under the root
Authentication headersRequired · stringAdapter name and JWT for this POST request. See Common headers
Content-TypeRequired headerExactly application/json
Content-LengthRequired headerUTF-8 byte length of the JSON body
nameBody · string · requiredOne new directory name, 1–255 characters; the only JSON field

name is the actual name, not a URL-encoded path. /, \, control characters, and names equal to . or .. are not allowed. For example, 2026/reports cannot create a nested path in one request.

Response

HTTP/1.1 204 No Content
ItemFormatContract
Status204 No ContentThe parent/name directory was created. Other 2xx statuses also succeed
BodyNoneNo created entry object is required. Retrieve it later with info or list
StatusConditions and handling
400 Bad RequestMissing name, incorrect type, extra fields, or an invalid name
404 Not FoundThe parent directory does not exist (public examples)
409 ConflictAn entry with the same name exists, or the parent is not a directory (public examples)
501 Not ImplementedAfter authentication, {"code":"MKDIR_NOT_SUPPORTED"}. No directory is created

The public examples return 409 even when repeating a creation request for an already created directory. If a response is lost, check whether the entry was created before retrying.

rename - Rename an entry

This optional endpoint renames a file or directory within the same parent directory. It defines no options for moving entries to another directory or overwriting an existing entry.

Request

POST /tfo-storage/v1/{path}/rename

POST /tfo-storage/v1/contracts/hello.txt/rename HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
Content-Type: application/json
Content-Length: 23

{"name":"greeting.txt"}
ItemFormatContract
pathPath · stringRelative path of the existing entry to rename. The Provider root cannot be renamed
Authentication headersRequired · stringAdapter name and JWT for this POST request. See Common headers
Content-TypeRequired headerExactly application/json
Content-LengthRequired headerUTF-8 byte length of the JSON body
nameBody · string · requiredOne new name, 1–255 characters; the only JSON field

Use the actual decoded name for name, following the mkdir name rules. Sending greeting.txt for contracts/hello.txt results in contracts/greeting.txt.

Response

HTTP/1.1 204 No Content
ItemFormatContract
Status204 No ContentThe entry was renamed within the same parent. Other 2xx statuses also succeed
BodyNoneNo new path or entry object is required. Use info on the new path or list on its parent to retrieve it
StatusConditions and handling
400 Bad RequestInvalid name, extra JSON fields, or an attempt to rename the Provider root
404 Not FoundNo entry exists at the original path
409 ConflictThe new name is already in use. The public examples also reject renaming locked entries
501 Not ImplementedAfter authentication, {"code":"RENAME_NOT_SUPPORTED"}. The original name is preserved

After a successful rename, a retry at the original path may find no target. If the response is lost, check both the original and new paths.

delete - Delete an entry

This optional endpoint deletes a file or directory. Reject attempts to delete the Provider root. The Provider must explicitly define whether it rejects nonempty directories or deletes them recursively; the public examples reject them with 409.

Request

DELETE /tfo-storage/v1/{path}/delete

DELETE /tfo-storage/v1/contracts/greeting.txt/delete HTTP/1.1
Host: storage.example.com
X-TFO-Storage-Adapter: customer-storage-a
X-TFO-Storage-Request-JWT: <SIGNED_REQUEST_JWT>
ItemFormatContract
pathPath · stringRelative path of the entry to delete. The Provider root cannot be deleted
Authentication headersRequired · stringAdapter name and JWT for this DELETE request. See Common headers
Request bodyNoneOmit Content-Type. Set the JWT body length to 0 and hash to the SHA-256 digest of an empty body

There are no query or body options such as recursive or force.

Response

HTTP/1.1 204 No Content
ItemFormatContract
Status204 No ContentThe entry was deleted. Other 2xx statuses also succeed
BodyNoneNo deleted entry object or deletion count is required
StatusConditions and handling
400 Bad RequestAttempt to delete the Provider root
404 Not FoundThe entry to delete does not exist, including a retry for an already deleted entry (public examples)
409 ConflictDeletion of a nonempty directory or locked entry was rejected (public examples)
501 Not ImplementedAfter authentication, {"code":"DELETE_NOT_SUPPORTED"}. The entry is not deleted

Unsupported operations

Use the following response only for an optional operation that is not implemented. Return it after authentication, body verification, and replay checks for that request. The status must be 501, the media type must be application/json, and the JSON body must contain only one code field matching the current operation. A charset parameter is allowed.

ItemFormatContract
StatusRequiredExactly 501 Not Implemented
Content-TypeRequired headerapplication/json; a charset parameter is allowed
codeBody · string · requiredThe exact value for the current operation from the table below. No additional fields
Body lengthBrief UTF-8 JSONThe complete object must fit within Office's 8 KiB error-body read limit

The fixed-length checks for successful read responses do not apply here, but you may include an exact Content-Length as in the example below.

HTTP/1.1 501 Not Implemented
Content-Type: application/json
Content-Length: 29

{"code":"LIST_NOT_SUPPORTED"}
OperationExact codeOffice behavior
listLIST_NOT_SUPPORTEDFile listing is unavailable. The connection can be saved after acknowledging the notice during registration
putPUT_NOT_SUPPORTEDSaving fails
lockLOCK_NOT_SUPPORTEDTreated as successful without an actual lock
unlockUNLOCK_NOT_SUPPORTEDTreated as successful without an actual unlock
mkdirMKDIR_NOT_SUPPORTEDDirectory creation is unavailable
renameRENAME_NOT_SUPPORTEDRenaming is unavailable
deleteDELETE_NOT_SUPPORTEDDeletion is unavailable

Because info and get are required, every 501 fails those operations. For other operations, an empty body, HTML, additional JSON fields, another operation's code, 404, or another 5xx status is not an unsupported declaration. For example, {"code":"LIST_NOT_SUPPORTED","message":"..."} or DELETE_NOT_SUPPORTED returned for list is invalid.

The only unsupported locking responses Office treats as successful are the exact LOCK_NOT_SUPPORTED and UNLOCK_NOT_SUPPORTED pair. Do not declare only one unsupported. Because no actual lock exists, concurrent saves follow the storage service's last-write or conflict policy. A 409 from implemented locking, authentication failures, network errors, and storage failures remain actual failures.

The admin connection test calls only list. Verify other capabilities through actual document operations; registration does not arbitrarily execute writes or deletes. Also see the unsupported operation JSON schema.

Error responses

Office treats 404 from info as an absent target. Except for that case and the exact unsupported responses above, non-2xx responses fail the Office operation. Ordinary errors have no fixed JSON body schema. Return a brief error description without secrets, JWTs, internal paths, document contents, or stack traces. Office may display up to 8 KiB of the error body.

ItemFormatContract
StatusHTTP status codeChoose according to the error cause below. The status determines success or failure
Content-TypeMatch the bodyNo fixed media type for ordinary errors. The public examples use text/plain; charset=utf-8
Bodytext or Provider-defined JSONOptional. Description strings in the public examples are not fixed protocol error identifiers

The following is an authentication error response. Do not distinguish unknown adapters, signature mismatches, expiration, and replay through detailed response messages.

HTTP/1.1 401 Unauthorized
Content-Type: text/plain; charset=utf-8
Content-Length: 12

Unauthorized
StatusWhen to use
400 Bad RequestInvalid path, JSON, body length, or a disallowed query
401 UnauthorizedUnknown adapter, invalid signature or claims, or an expired or reused JWT
403 ForbiddenNo permission to access the storage or document
404 Not FoundTarget entry or request path does not exist
405 Method Not AllowedIncorrect method for a known operation path (public examples)
409 ConflictName, type, or lock-owner conflict, or rejection of nonempty directory deletion
411 Length RequiredRequired body Content-Length is missing
413 Payload Too LargeRequest body, document, or listing exceeds its limit, or a Provider-defined limit is exceeded
415 Unsupported Media TypeRequest Content-Type does not match the operation contract
500 Internal Server ErrorUnhandled internal Provider error (public examples). Do not expose exception details in the response
501 Not ImplementedExact unsupported declaration for an optional operation as specified above
503 Service UnavailableTemporary storage failure

Do not turn authentication errors into unsupported responses or report storage failures as successful. Preserve the existing file and clean up temporary files when put fails. Do not retry oversized requests or responses using chunked transfer.

The public examples separately limit JSON request bodies to 16 KiB. This is a different limit from the 5 MiB response limit for info and list. See Example configuration and setup for deployment-specific limits.