본문으로 건너뛰기

HTTP Storage Protocol

HTTP Storage Protocol v1은 Office의 HTTP Storage 어댑터와 스토리지 서비스를 직접 운영하는 HTTP Storage Provider 사이의 요청·응답 규격입니다. Provider가 아래 endpoint를 구현하며 Office가 요청을 보냅니다. Office에 연결하는 방법과 예제는 HTTP Storage에서 안내합니다.

이 문서는 9개 endpoint의 메서드·경로, 요청 헤더와 본문, 응답 필드와 상태 코드를 정의합니다. 공통 인증·경로·크기 제한은 모든 endpoint에 적용하며, 각 작업에서 추가로 필요한 조건은 해당 절에서 설명합니다.

URL과 경로

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

Provider base URL은 HTTP 또는 HTTPS 주소이며 경로 접두사를 포함할 수 있습니다. 사용자 정보, 쿼리 문자열, fragment, .·.. 경로는 허용하지 않습니다. 끝의 /는 제거한 뒤 프로토콜 경로를 붙입니다.

문서 경로는 Provider 루트에 상대적입니다. 각 UTF-8 경로 세그먼트를 따로 퍼센트 인코딩하고 /로 연결합니다. 루트는 빈 문서 경로이며 /tfo-storage/v1/list처럼 요청합니다. 중간에 빈 세그먼트나 추가 /를 넣지 않습니다.

항목예시
Provider base URLhttps://storage.example.com/office
문서 경로contracts/Proposal 2026.docx
문서 정보 요청GET /office/tfo-storage/v1/contracts/Proposal%202026.docx/info
루트 목록 요청GET /office/tfo-storage/v1/list

Provider는 디코딩한 경로를 자신의 저장소 루트 아래에서만 처리합니다. .·.., 경로 세그먼트 안의 /·\, 제어 문자, 잘못된 UTF-8 인코딩과 심볼릭 링크를 통한 루트 이탈을 거부합니다. 쿼리 매개변수, 쿠키, 리디렉션, 임의의 인증 헤더 전달은 사용하지 않습니다. 프록시를 거쳐도 JWT 검증에 쓰는 원시 인코딩 경로가 유지되어야 합니다.

공통 요청 헤더

헤더적용 범위
X-TFO-Storage-Adapter모든 요청, 필수등록한 어댑터 이름. 영문·숫자로 시작하는 1~128자의 영문·숫자·.·_·-
X-TFO-Storage-Request-JWT모든 요청, 필수아래 규칙으로 서명한 JWT 원문. Bearer 접두사를 붙이지 않음
Content-Typeput, lock, unlock, mkdir, renameputapplication/octet-stream, JSON 작업은 application/json
Content-Length본문이 있는 요청, 필수정확한 본문 바이트 수를 나타내는 10진수 정수 하나

info, list, get, delete 요청에는 본문과 Content-Type이 없습니다. 서명에는 본문 길이 0과 빈 본문의 SHA-256을 넣습니다. 본문은 multipart나 base64로 감싸지 않으며 chunked 업로드와 압축을 사용하지 않습니다. JSON 본문은 UTF-8로 직렬화한 실제 바이트를 서명합니다.

JWT 서명과 검증

Provider는 문서를 읽거나 변경하기 전에 요청이 연결된 Office 어댑터의 시크릿으로 서명됐는지, 전송 중 메서드·경로·본문이 바뀌지 않았는지 확인해야 합니다. 이를 위해 Office는 공유 시크릿으로 요청 정보를 담은 JWT에 서명하고, Provider는 같은 시크릿으로 서명을 검증한 뒤 실제 요청과 비교합니다. 짧은 유효 기간과 요청별 고유 ID를 함께 검사해 이미 사용한 요청의 재전송도 거부합니다.

서명은 요청의 무결성을 보호하며 문서 내용을 암호화하지는 않습니다. 전송 내용의 보호에는 HTTPS를 사용하고, 사용자·테넌트별 문서 접근 권한은 Provider가 별도로 확인합니다.

Office와 Provider는 어댑터별로 UTF-8 기준 최소 32바이트의 무작위 시크릿을 공유합니다. HMAC 키는 이 문자열의 UTF-8 바이트입니다. 시크릿을 요청 URL이나 본문으로 전달하지 않습니다.

JWT는 compact JWS 형식입니다. 헤더와 payload를 각각 UTF-8 JSON으로 직렬화하고 padding 없는 Base64URL로 인코딩합니다. 두 값을 .으로 연결한 바이트에 HMAC-SHA256을 적용한 뒤, 서명을 같은 Base64URL로 인코딩해 마지막에 붙입니다.

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

다음은 본문 hello(5바이트)를 저장하는 요청의 payload 예시입니다. iat, exp, jti는 실제 호출마다 새로 생성합니다.

{
"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"}
}
}
필드타입필수검증 규칙
헤더 algstring정확히 HS256
헤더 typstring정확히 tfo-storage-request+jwt
issstring정확히 thinkfree-office
audstring 또는 원소 1개의 배열유일한 대상이 tfo-http-storage-provider
iatinteger발급 Unix 시각(초). 현재 시각보다 미래가 아님
expinteger만료 Unix 시각(초). 현재 시각보다 뒤이며 0 < exp - iat <= 60
jtistring요청마다 고유한 ID. Office는 UUID를 생성하며 공개 예제는 1~64자를 허용
requestobject아래 서명된 요청 필드를 담는 객체
request.adapterstring실제 어댑터 헤더 및 Provider에 설정한 연결 이름과 일치
request.methodstring실제 대문자 HTTP 메서드와 일치
request.pathstringProvider base path를 포함한 실제 원시 인코딩 경로와 일치. scheme·host·query는 포함하지 않음
request.content_typestring헤더가 있을 때실제 Content-Type과 정확히 일치
request.content_lengthinteger실제 수신한 본문의 바이트 수. 본문이 없으면 0
request.content_sha256string실제 본문 바이트의 SHA-256, 소문자 16진수 64자리
request.office_connection_idstring아니요Office의 불투명한 연결 컨텍스트. 사용자 인증 근거로 사용하지 않음
request.argumentsobject아니요save_type 등 작업 컨텍스트. 접근 권한을 부여하는 값이 아님
request.client_metadataobject아니요호출자가 제공한 JSON. 전송 무결성만 보장하며 Office가 확인한 사용자 신원으로 신뢰하지 않음

빈 본문의 SHA-256은 다음과 같습니다.

e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

전체 JWT는 최대 5,120바이트입니다. client_metadata는 UTF-8 JSON 기준 2,048바이트 이하이며 객체 키는 공백이 아닌 1~64자, 문자열 값은 최대 512자입니다. 루트 객체의 깊이를 0으로 세어 최대 깊이 8을 허용합니다. 비밀값이나 문서 내용을 넣지 않습니다.

검증 순서

  1. 실제 HTTP 메서드와 원시 경로, 쿼리 유무, 미디어 유형과 고정 길이를 검사합니다. 길이가 제한을 넘으면 본문을 읽거나 임시 보관하기 전에 거부합니다.
  2. 허용된 본문을 제한된 메모리 또는 임시 파일로 읽으면서 실제 길이와 SHA-256을 계산합니다. 업로드가 끝나기 전에는 대상 문서를 교체하지 않습니다.
  3. 어댑터 헤더로 설정된 시크릿을 찾아 JWT 서명·헤더·발급자·대상·시간을 검증합니다.
  4. 서명된 어댑터·메서드·원시 경로·본문 유형·길이·해시를 실제 요청과 비교합니다.
  5. jti를 원자적으로 기록하고 exp까지 재사용을 거부합니다. Provider가 여러 인스턴스라면 같은 재전송 방지 저장소를 사용합니다.
  6. 문서 접근 권한을 확인하고 작업을 실행하거나, 선택적 작업에 대한 정확한 미지원 응답을 반환합니다.

서명 오류나 재사용된 JWT는 저장소에 접근하거나 기능 미지원 여부를 응답하기 전에 거부합니다. 서명 검증은 Provider가 담당하는 사용자·테넌트별 문서 접근 권한 검사를 대체하지 않습니다. 서버의 시각을 동기화하고, 같은 작업을 재요청할 때도 새 iat·exp·jti로 서명합니다. jti는 중복 작업의 성공 결과를 돌려주는 멱등 키가 아닙니다. 응답이 유실되면 저장소 상태를 먼저 확인한 뒤 작업 특성에 맞게 재시도합니다.

응답 형식과 크기 제한

성공한 info, list, get, put 응답에는 정확한 10진수 Content-Length 헤더가 하나 있어야 합니다. 값은 실제 전송한 바이트 수와 같아야 하며, Transfer-EncodingContent-Encoding은 허용하지 않습니다. 미디어 유형의 charset 매개변수는 허용하지만 JSON 본문은 UTF-8이어야 합니다.

대상Content-Type제한
info, list, put 성공 응답application/json각각 5 MiB (5,242,880바이트)
get 성공 응답application/octet-stream300 MiB (314,572,800바이트)
put 요청application/octet-stream300 MiB (314,572,800바이트)
메타데이터의 파일 sizeJSON integer0~314,572,800
listentriesJSON array최대 10,000개이며 전체 JSON도 5 MiB 이하

JSON은 한 번만 직렬화해 바이트 길이를 구하고 그 바이트 그대로 전송합니다. get은 응답 헤더를 보내기 전에 저장소에서 원본 크기를 확인하고 같은 길이만큼 스트리밍합니다. 크기를 알아내려고 문서 전체를 메모리에 올리지 않습니다.

Provider는 더 낮은 문서 제한을 둘 수 있지만 300 MiB를 넘길 수는 없습니다. 파일 메타데이터를 처리하거나 get을 전송하거나 put을 임시 보관하기 전에 검사합니다. 최대값은 포함되므로 5,242,880바이트 JSON과 314,572,800바이트 문서는 제한 내이고, 각각 1바이트 큰 5,242,881314,572,801은 거부합니다.

길이 누락·중복·음수·소수, 압축·chunked 응답, 잘못된 미디어 유형, 조기 EOF와 실제 길이 불일치는 현재 작업의 실패입니다. Office가 잘못된 응답을 거부한 뒤에도 Provider는 후속 요청을 처리할 수 있어야 합니다. 인증된 501과 일반 오류 응답에는 아래 별도 규칙을 적용합니다.

성공 상태와 본문

작업정상 응답본문 규격
info, list200 OKJSON 객체가 필수. 빈 본문이나 204로 대체하지 않음
get200 OK파일 전체 바이트. 빈 파일은 Content-Length: 0으로 반환
put200 OKdocId를 포함하는 JSON 객체가 필수. PUT 응답 참고
lock, unlock, mkdir, rename, delete204 No Content본문 없음

Office는 2xx 상태를 성공으로 분류하지만 info·list·get·put에서는 위 미디어 유형·고정 길이·본문 검증도 통과해야 합니다. lock·unlock·mkdir·rename·delete는 다른 2xx도 허용하며 응답 JSON의 특정 필드를 요구하지 않습니다. 204에는 본문을 보내지 않습니다. 이 다섯 작업에서 200 응답으로 결과 텍스트를 보낸다면 text/plain; charset=utf-8을 사용하고, Office가 읽는 상한인 10 MiB 이내로 작성합니다. 이 결과 텍스트로 문서 내용을 반환하지 않습니다.

메타데이터 필드

info는 항목 객체 하나, listentries 배열을 가진 객체 하나를 반환합니다. 항목 객체에 아래 이외의 필드를 넣지 않습니다.

필드타입필수의미와 제한
pathstring루트에 상대적인 디코딩된 경로, 최대 4,096자. 루트는 빈 문자열
namestring마지막 경로 세그먼트와 동일한 1~255자 이름. 루트는 고정된 표시 이름
typestringfile 또는 directory
sizeinteger파일 바이트 수, 0~314,572,800. 디렉터리는 반드시 0
readableboolean문서를 읽을 수 있는지 여부
writableboolean문서를 쓸 수 있는지 여부
lockedboolean현재 잠금 상태
lockerstring 또는 null아니요잠금 소유자, 최대 255자
createdAtstring 또는 null아니요RFC 3339 생성 시각, 최대 64자
modifiedAtstring 또는 null아니요RFC 3339 수정 시각, 최대 64자
revisionstring 또는 null아니요저장소 revision 또는 ETag에 해당하는 값, 최대 1,024자

info.path는 요청한 문서 경로와 같아야 합니다. list는 요청한 디렉터리의 바로 아래 항목만 포함하고 같은 경로를 중복해서 반환하지 않습니다. 페이지네이션 매개변수는 없습니다. 잘못된 타입·시각·이름·크기, 알 수 없는 필드, 중복 경로와 하위 디렉터리 내부의 항목은 거부됩니다.

필수 필드는 null을 허용하지 않습니다. 선택 필드는 생략하거나 null로 반환할 수 있습니다. path는 URL 인코딩한 문자열이 아니라 디코딩된 경로이며, createdAt·modifiedAt2026-09-14T00:00:00Z처럼 시간대가 포함된 시각입니다. readable·writable은 항목의 접근 가능 여부를 알리는 값이며, 실제 요청의 권한 검사도 계속 수행합니다. revision은 저장소의 버전 정보이며 이 프로토콜의 조건부 요청 헤더나 잠금 토큰으로 자동 사용되지 않습니다.

기계 검증에는 entry schema, info response schema, list response schema를 사용할 수 있습니다. 스키마 검증에 더해 위 경로 관계와 런타임 제한도 확인합니다.

작업별 Request·Response

path는 대상 항목, parent는 새 디렉터리를 만들 부모의 Provider 루트 아래 상대 경로입니다. 경로 전체가 아니라 각 세그먼트를 URL 규칙에 따라 인코딩합니다. 모든 요청에는 공통 인증 헤더X-TFO-Storage-AdapterX-TFO-Storage-Request-JWT가 필수이며, 쿼리 매개변수는 없습니다.

JSON 요청은 아래에 명시한 필드 하나만 가진 객체입니다. 필수 필드의 생략·null·다른 타입·추가 필드를 허용하지 않습니다. JSON 요청의 Content-Type은 정확히 application/json이며, Content-Length는 UTF-8 직렬화 결과의 바이트 수입니다. 공개 예제의 JSON 요청 제한은 16 KiB입니다. 응답의 5 MiB 제한과 구분합니다.

각 절의 오류 표는 작업별 조건입니다. 모든 endpoint에는 공통 오류의 경로·인증·권한·본문 형식·저장소 장애 조건도 적용합니다. Office가 모든 Provider에 동일한 일반 오류 본문을 강제하지는 않으며, 공개 예제에만 해당하는 충돌·파일시스템 정책은 별도로 표시합니다.

작업메서드경로 접미사구현
infoGET/{path}/info필수
listGET/{path}/list선택
getGET/{path}/get필수
putPUT/{path}/put선택
lockPOST/{path}/lockunlock과 함께 선택
unlockPOST/{path}/unlocklock과 함께 선택
mkdirPOST/{parent}/mkdir선택
renamePOST/{path}/rename선택
deleteDELETE/{path}/delete선택

아래는 base URL이 https://storage.example.com인 예시입니다. 모든 요청의 <SIGNED_REQUEST_JWT>그 요청의 메서드·경로·본문을 서명한 새 JWT로 바꿉니다. 예시 본문은 코드 블록 마지막 줄바꿈을 포함하지 않으며, 표시한 Content-Length는 그 기준입니다. hello.txthello는 원시 파일 바이트를 보여 주는 5바이트 예시입니다.

info - 문서 정보 조회

문서 또는 디렉터리의 메타데이터를 조회하는 필수 endpoint입니다. 문서 내용은 반환하지 않습니다.

요청

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>
항목형식규격
path경로 · string파일 또는 디렉터리의 상대 경로. 루트는 /tfo-storage/v1/info
인증 헤더필수 · string어댑터 이름과 이 GET 요청의 JWT. 공통 헤더 참고
요청 본문없음Content-Type 생략. JWT의 본문 길이는 0, 해시는 빈 본문의 SHA-256

응답

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"}
항목형식규격
상태200 OK대상 항목의 메타데이터 조회 성공
Content-Type필수 헤더application/json
Content-Length필수 헤더응답 JSON의 정확한 바이트 수. 최대 5,242,880
본문object메타데이터 필드를 직접 담은 항목 객체 하나. entrydata로 감싸지 않음

필수 필드는 path, name, type, size, readable, writable, locked입니다. 선택 필드는 locker, createdAt, modifiedAt, revision이며 생략 또는 null을 허용합니다. 각 필드의 타입·길이·허용값은 공통 항목 스키마를 따릅니다. 응답 path는 요청 경로를 디코딩한 값과 같아야 합니다. 루트는 path: "", type: "directory", size: 0을 사용합니다.

상태반환 조건과 처리
404 Not Found대상이 없음. Office는 항목이 없는 것으로 처리하며 가짜 메타데이터를 요구하지 않음
413 Payload Too Large파일 크기 또는 응답 JSON이 제한을 넘음
501 Not Implemented필수 endpoint이므로 기능 미지원으로 인정하지 않음

list - 하위 항목 조회

디렉터리의 바로 아래 파일과 디렉터리를 조회하는 선택 endpoint입니다. 재귀 검색이나 페이지네이션은 제공하지 않습니다.

요청

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>
항목형식규격
path경로 · string조회할 디렉터리. 루트는 /tfo-storage/v1/list
인증 헤더필수 · string어댑터 이름과 이 GET 요청의 JWT. 공통 헤더 참고
요청 본문없음Content-Type 생략. JWT의 본문 길이는 0, 해시는 빈 본문의 SHA-256

응답

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"}]}
항목형식규격
상태200 OK디렉터리 조회 성공. 빈 디렉터리도 성공
Content-Type필수 헤더application/json
Content-Length필수 헤더전체 JSON의 정확한 바이트 수. 최대 5,242,880
entries본문 · array · 필수0~10,000개의 항목 객체. 최상위 객체의 유일한 필드
entries[]objectinfo와 같은 필수·선택 필드. 디코딩된 path는 조회 디렉터리의 바로 아래 경로이며 중복 불가

항목의 경로는 항상 루트 기준입니다. 루트 목록에는 contracts, contracts의 목록에는 contracts/hello.txt처럼 반환합니다. 현재 디렉터리 자신, 상위 디렉터리, 하위 폴더 내부 항목을 섞지 않습니다. 순서는 규격에서 정하지 않으며 total, cursor, hasMore 같은 추가 필드를 보내지 않습니다.

빈 디렉터리는 200{"entries":[]}를 반환합니다. 이 본문의 길이는 14바이트입니다. 목록 기능을 구현하지 않았다면 인증 후 501LIST_NOT_SUPPORTED를 반환합니다. 빈 목록, 대상이 없는 404, 기능 미지원은 서로 다른 결과입니다.

상태반환 조건과 처리
404 Not Found조회할 디렉터리가 없음
409 Conflict대상이 디렉터리가 아님(공개 예제)
413 Payload Too Large항목 수·메타데이터 크기·포함된 파일 크기가 제한을 넘음. 일부 목록을 성공으로 반환하지 않음
501 Not Implemented인증 후 {"code":"LIST_NOT_SUPPORTED"}. Office에서 파일 목록을 사용할 수 없음

get - 문서 다운로드

파일의 전체 원본 바이트를 반환하는 필수 endpoint입니다. Office는 이 내용을 읽어 문서를 엽니다.

요청

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>
항목형식규격
path경로 · string다운로드할 파일. 디렉터리의 목록은 list 사용
인증 헤더필수 · string어댑터 이름과 이 GET 요청의 JWT. 공통 헤더 참고
요청 본문없음Content-Type 생략. JWT의 본문 길이는 0, 해시는 빈 본문의 SHA-256

부분 다운로드용 Range 요청은 이 계약에 없습니다. 다운로드 URL이나 JSON·Base64 대신 파일 자체를 반환합니다.

응답

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

hello
항목형식규격
상태200 OK파일 전체 다운로드 성공
Content-Type필수 헤더파일 확장자와 관계없이 application/octet-stream
Content-Length필수 헤더실제 파일 바이트 수. 0~314,572,800
본문binaryContent-Length와 정확히 같은 길이의 원본 파일. JSON 필드 없음

빈 파일은 200Content-Length: 0으로 반환합니다. 압축·chunked·리디렉션·부분 응답으로 대체하지 않습니다. 스트리밍 중 파일이 바뀌어 헤더의 길이와 달라지지 않도록 저장소에서 일관된 내용을 읽어야 합니다.

상태반환 조건과 처리
404 Not Found파일이 없음
409 Conflict대상이 파일이 아님(공개 예제)
413 Payload Too Large파일이 허용 크기를 넘음. 성공 헤더나 파일 본문을 보내기 전에 거부
501 Not Implemented필수 endpoint이므로 기능 미지원으로 인정하지 않음

put - 문서 저장

편집이 끝난 전체 파일을 원시 바이트로 받습니다. 부분 업데이트나 multipart 업로드는 사용하지 않습니다. 모든 바이트를 임시 보관하고 인증·길이·해시·접근 권한을 검증한 뒤 대상 파일을 원자적으로 교체합니다. 실패하면 기존 파일을 보존합니다.

요청

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
항목형식규격
path경로 · string저장할 파일의 상대 경로. 빈 경로로 Provider 루트를 덮어쓸 수 없음
인증 헤더필수 · string어댑터 이름과 이 PUT 요청의 JWT. 공통 헤더 참고
Content-Type필수 헤더정확히 application/octet-stream
Content-Length필수 헤더전체 파일의 실제 바이트 수. 0~314,572,800
요청 본문binary파일 전체 바이트. JSON 객체·Base64·multipart 형식이 아님
request.arguments.save_typeJWT · string · 선택Office가 전달하는 저장 컨텍스트. 예: save. 별도 본문 필드나 쿼리로 전달하지 않음

save_type은 접근 권한이나 잠금 소유자를 증명하지 않습니다. 조건부 저장용 revision·If-Match·잠금 토큰 필드는 이 요청에 정의돼 있지 않습니다. 동시 저장을 허용하거나 충돌을 거부하는 정책은 Provider가 정합니다. 공개 예제는 부모 디렉터리가 있으면 파일을 생성하거나 교체하며, 부모 디렉터리는 자동으로 만들지 않습니다.

응답

저장소 반영을 완료한 뒤 저장된 문서의 식별자를 JSON 객체로 반환합니다.

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

{"docId":"saved-document-id"}
항목형식규격
상태200 OK본문 검증을 통과하는 다른 2xx도 허용. 204 No Content는 허용하지 않음
Content-Type필수 헤더UTF-8 application/json
Content-Length필수 헤더실제 JSON 본문 바이트 수. 고정 길이 규칙 적용
본문JSON object · 필수docId 하나만 포함. 추가·중복 필드, 뒤따르는 JSON 값이나 텍스트는 허용하지 않음
docIdstring · 필수ASCII 영문·숫자로 시작하는 1~1,024자의 ASCII 영문·숫자·.·_·:·-. 대소문자와 관계없이 true·false는 예약값

docId는 실제 저장된 대상 문서를 식별합니다. 같은 어댑터의 같은 저장 경로는 내용이 바뀌어도 같은 ID를 유지하고, 다른 경로는 다른 ID를 사용합니다. 일반 저장과 다른 이름으로 저장 모두 이 규칙을 따릅니다. revision이나 원본 세션 ID로 대체하지 않습니다.

빈 본문, 맨몸 ID, XML, JSON 문자열·숫자·불리언(true 포함)·null·배열은 거부합니다. 값을 trim하거나 다른 형식으로 자동 변환하지 않습니다. 기계 판독용 계약은 PUT 응답 스키마를 참고합니다. Node.js·Spring Boot·FastAPI 예제는 디코딩한 루트 기준 상대 경로를 UTF-8로 인코딩한 뒤 SHA-256을 계산하고, 64자의 소문자 16진수 문자열을 ID로 반환합니다. 실제 Provider는 위 형식에 맞는 자체 문서 ID를 사용할 수 있습니다.

상태반환 조건과 처리
400 Bad Request빈 문서 경로, 선언한 길이와 실제 본문 불일치 등
404 Not Found부모 디렉터리가 없음(공개 예제)
409 Conflict파일 위치에 디렉터리가 있거나 저장소의 충돌 정책에 따라 저장을 거부함
413 Payload Too Large문서가 프로토콜 또는 Provider의 더 낮은 제한을 넘음
501 Not Implemented인증 후 {"code":"PUT_NOT_SUPPORTED"}. 저장소에 반영하지 않으며 Office에는 저장 실패로 표시

실패 시 기존 파일을 보존하고 임시 데이터를 정리합니다. 같은 경로에 다시 저장하면 내용이나 revision이 바뀌어도 같은 docId를 반환합니다.

lock - 문서 잠금

문서에 잠금 소유자를 기록하는 선택 endpoint입니다. 같은 소유자의 재요청은 성공하고, 다른 소유자가 잠근 상태면 충돌로 처리합니다. unlock과 함께 구현합니다.

요청

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"}
항목형식규격
path경로 · string잠글 문서의 상대 경로
인증 헤더필수 · string어댑터 이름과 이 POST 요청의 JWT. 공통 헤더 참고
Content-Type필수 헤더정확히 application/json
Content-Length필수 헤더JSON 본문의 UTF-8 바이트 수
owner본문 · string · 필수비어 있지 않은 잠금 소유자 식별자. JSON의 유일한 필드

owner는 그대로 저장하고 해제 요청에서 동일한 값인지 비교합니다. 사용자 인증 토큰으로 사용하지 않습니다. info·listlocker에 반환할 값은 해당 필드의 최대 255자 제한에 맞춰야 합니다. 잠금 만료 시각·임대 기간·갱신 주기는 이 요청의 필드로 정의하지 않습니다.

응답

HTTP/1.1 204 No Content
항목형식규격
상태204 No Content새 잠금 획득 또는 같은 owner의 기존 잠금 확인. 다른 2xx도 성공
본문없음잠금 토큰이나 결과 JSON을 요구하지 않음. 다른 2xx의 본문도 성공 판단에 필요한 필드가 아님
상태반환 조건과 처리
400 Bad Requestowner 생략·빈 문자열·잘못된 타입·추가 JSON 필드
404 Not Found잠글 대상이 없음(공개 예제)
409 Conflict다른 owner가 잠근 상태. 기존 잠금을 바꾸지 않음
501 Not Implemented인증 후 {"code":"LOCK_NOT_SUPPORTED"}. Office는 실제 잠금 없이 성공으로 처리

같은 소유자로 다시 잠글 때도 JWT는 새로 생성합니다. 미지원이면 unlock도 미지원이어야 하며, 실제 잠금 충돌을 LOCK_NOT_SUPPORTED로 바꾸지 않습니다.

unlock - 잠금 해제

현재 소유자의 잠금을 해제하는 선택 endpoint입니다. 잠금이 이미 없으면 성공하고, 다른 소유자의 잠금을 해제하려 하면 충돌로 처리합니다. lock과 함께 구현합니다.

요청

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"}
항목형식규격
path경로 · string잠금을 해제할 문서의 상대 경로
인증 헤더필수 · string어댑터 이름과 이 POST 요청의 JWT. 공통 헤더 참고
Content-Type필수 헤더정확히 application/json
Content-Length필수 헤더JSON 본문의 UTF-8 바이트 수
owner본문 · string · 필수잠글 때 사용한 소유자 식별자와 같은 값. JSON의 유일한 필드

owner를 생략하거나 다른 값으로 보내 강제 해제하는 기능은 없습니다.

응답

HTTP/1.1 204 No Content
항목형식규격
상태204 No Content같은 소유자의 잠금 해제 또는 이미 잠금이 없는 상태. 다른 2xx도 성공
본문없음해제 결과 JSON을 요구하지 않음
상태반환 조건과 처리
400 Bad Requestowner 생략·빈 문자열·잘못된 타입·추가 JSON 필드
409 Conflict현재 잠금 소유자가 다름. 기존 잠금을 유지
501 Not Implemented인증 후 {"code":"UNLOCK_NOT_SUPPORTED"}. Office는 실제 해제 작업 없이 성공으로 처리

이미 해제된 잠금에 대한 재요청도 새 JWT로 보냅니다. 미지원이면 lock도 미지원이어야 합니다.

mkdir - 디렉터리 생성

지정한 부모 디렉터리 바로 아래에 새 디렉터리 하나를 만드는 선택 endpoint입니다. 중간 디렉터리를 포함한 여러 단계의 경로를 생성하는 요청이 아닙니다.

요청

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"}
항목형식규격
parent경로 · string기존 부모 디렉터리의 상대 경로. 루트 아래 생성은 /tfo-storage/v1/mkdir
인증 헤더필수 · string어댑터 이름과 이 POST 요청의 JWT. 공통 헤더 참고
Content-Type필수 헤더정확히 application/json
Content-Length필수 헤더JSON 본문의 UTF-8 바이트 수
name본문 · string · 필수새 디렉터리 이름 하나. 1~255자이며 JSON의 유일한 필드

name은 URL 인코딩한 경로가 아닌 실제 이름입니다. /·\·제어 문자와 이름 전체가 . 또는 ..인 값은 허용하지 않습니다. 예를 들어 2026/reports를 넣어 하위 경로를 한꺼번에 만들 수 없습니다.

응답

HTTP/1.1 204 No Content
항목형식규격
상태204 No Contentparent/name 디렉터리 생성 완료. 다른 2xx도 성공
본문없음생성한 항목 객체를 요구하지 않음. 이후 info 또는 list로 조회 가능
상태반환 조건과 처리
400 Bad Requestname 생략·잘못된 타입·추가 필드·허용하지 않는 이름
404 Not Found부모 디렉터리가 없음(공개 예제)
409 Conflict같은 이름의 항목이 있거나 부모가 디렉터리가 아님(공개 예제)
501 Not Implemented인증 후 {"code":"MKDIR_NOT_SUPPORTED"}. 디렉터리를 생성하지 않음

공개 예제는 이미 만들어진 디렉터리에 같은 생성 요청을 보내도 409를 반환합니다. 응답이 유실됐다면 먼저 항목이 생성됐는지 확인합니다.

rename - 이름 변경

파일 또는 디렉터리의 이름을 같은 부모 디렉터리 안에서 바꾸는 선택 endpoint입니다. 다른 디렉터리로 이동하거나 기존 항목을 덮어쓰는 옵션은 정의하지 않습니다.

요청

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"}
항목형식규격
path경로 · string이름을 바꿀 기존 항목의 상대 경로. Provider 루트는 변경 불가
인증 헤더필수 · string어댑터 이름과 이 POST 요청의 JWT. 공통 헤더 참고
Content-Type필수 헤더정확히 application/json
Content-Length필수 헤더JSON 본문의 UTF-8 바이트 수
name본문 · string · 필수새 이름 하나. 1~255자이며 JSON의 유일한 필드

name에는 디코딩된 실제 이름을 넣고 mkdir의 이름 규칙을 적용합니다. contracts/hello.txtgreeting.txt를 보내면 결과 경로는 contracts/greeting.txt입니다.

응답

HTTP/1.1 204 No Content
항목형식규격
상태204 No Content같은 부모 안에서 이름 변경 완료. 다른 2xx도 성공
본문없음새 경로나 항목 객체를 요구하지 않음. 새 경로의 info 또는 부모의 list로 조회 가능
상태반환 조건과 처리
400 Bad Request잘못된 name, 추가 JSON 필드 또는 Provider 루트 변경 시도
404 Not Found원래 경로의 항목이 없음
409 Conflict새 이름이 이미 사용 중. 공개 예제는 잠긴 항목의 이름 변경도 거부
501 Not Implemented인증 후 {"code":"RENAME_NOT_SUPPORTED"}. 원래 이름을 유지

이름 변경 후 원래 경로로 재요청하면 대상이 없을 수 있습니다. 응답이 유실됐다면 원래 경로와 새 경로의 상태를 확인합니다.

delete - 항목 삭제

파일 또는 디렉터리를 삭제하는 선택 endpoint입니다. Provider 루트 삭제는 거부합니다. 비어 있지 않은 디렉터리를 거부할지 재귀 삭제할지는 Provider가 명시적으로 정해야 하며, 공개 예제는 409로 거부합니다.

요청

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>
항목형식규격
path경로 · string삭제할 항목의 상대 경로. Provider 루트는 삭제 불가
인증 헤더필수 · string어댑터 이름과 이 DELETE 요청의 JWT. 공통 헤더 참고
요청 본문없음Content-Type 생략. JWT의 본문 길이는 0, 해시는 빈 본문의 SHA-256

recursive, force 같은 쿼리·본문 옵션은 없습니다.

응답

HTTP/1.1 204 No Content
항목형식규격
상태204 No Content항목 삭제 완료. 다른 2xx도 성공
본문없음삭제한 항목 객체나 삭제 개수를 요구하지 않음
상태반환 조건과 처리
400 Bad RequestProvider 루트 삭제 시도
404 Not Found삭제할 항목이 없음. 이미 삭제한 항목의 재요청도 여기에 해당(공개 예제)
409 Conflict비어 있지 않은 디렉터리 또는 잠긴 항목 삭제 거부(공개 예제)
501 Not Implemented인증 후 {"code":"DELETE_NOT_SUPPORTED"}. 항목을 삭제하지 않음

기능 미지원 응답

선택적 작업을 구현하지 않았을 때만 아래 응답을 사용합니다. 해당 요청의 인증·본문 검증·재전송 검사를 마친 뒤 반환해야 합니다. 상태는 501, 미디어 유형은 application/json, JSON 본문은 현재 작업과 일치하는 code 하나만 포함합니다. charset 매개변수는 허용합니다.

항목형식규격
상태필수정확히 501 Not Implemented
Content-Type필수 헤더application/json. charset 매개변수 허용
code본문 · string · 필수아래 표의 현재 작업에 해당하는 정확한 값. 다른 필드 추가 불가
본문 길이짧은 UTF-8 JSONOffice의 오류 본문 읽기 한도 8 KiB 안에 전체 객체가 들어가야 함

성공한 읽기 응답의 고정 길이 검사와는 별개이지만, 아래 예시처럼 정확한 Content-Length를 함께 반환할 수 있습니다.

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

{"code":"LIST_NOT_SUPPORTED"}
작업정확한 codeOffice 동작
listLIST_NOT_SUPPORTED목록 기능 사용 불가. 등록 시 안내를 확인하고 연결을 저장할 수 있음
putPUT_NOT_SUPPORTED저장 실패
lockLOCK_NOT_SUPPORTED실제 잠금 없이 성공으로 처리
unlockUNLOCK_NOT_SUPPORTED실제 잠금 해제 없이 성공으로 처리
mkdirMKDIR_NOT_SUPPORTED디렉터리 생성 불가
renameRENAME_NOT_SUPPORTED이름 변경 불가
deleteDELETE_NOT_SUPPORTED삭제 불가

infoget은 필수이므로 모든 501을 작업 실패로 처리합니다. 다른 작업에서도 빈 본문, HTML, 추가 JSON 필드, 다른 작업의 code, 404나 다른 5xx는 미지원 선언이 아닙니다. 예를 들어 {"code":"LIST_NOT_SUPPORTED","message":"..."} 또는 list에 대한 DELETE_NOT_SUPPORTED는 잘못된 응답입니다.

Office가 성공으로 처리하는 잠금 응답은 정확한 LOCK_NOT_SUPPORTEDUNLOCK_NOT_SUPPORTED뿐입니다. 둘 중 하나만 미지원으로 선언하면 안 됩니다. 실제 잠금이 없으므로 동시 저장 결과는 저장소의 마지막 쓰기 또는 충돌 정책을 따릅니다. 구현된 잠금의 409, 인증 실패, 네트워크 오류와 저장소 장애는 실제 실패로 유지됩니다.

관리자 연결 테스트는 list만 호출합니다. 나머지 기능은 실제 문서 작업에서 확인하며 등록 과정에서 쓰기·삭제 작업을 임의로 실행하지 않습니다. 기능 미지원 JSON schema도 함께 참고합니다.

오류 응답

info404는 대상 없음으로 처리합니다. 이 경우와 위의 정확한 기능 미지원 응답을 제외하면 2xx가 아닌 응답은 Office 작업 실패입니다. 일반 오류 본문에는 고정된 JSON 스키마가 없습니다. 짧은 오류 설명을 반환하되 시크릿, JWT, 내부 경로, 문서 내용, 스택 추적을 포함하지 않습니다. Office에 오류 본문의 최대 8 KiB가 표시될 수 있습니다.

항목형식규격
상태HTTP 상태 코드아래 오류 원인에 맞게 선택. 상태가 성공 여부를 결정
Content-Type본문에 맞게 지정일반 오류는 고정 미디어 유형이 없음. 공개 예제는 text/plain; charset=utf-8
본문text 또는 Provider가 정의한 JSON선택 사항. 공개 예제의 설명 문자열도 프로토콜의 고정 오류 식별자가 아님

인증 오류 응답 예시는 다음과 같습니다. 알 수 없는 어댑터·서명 불일치·만료·재사용을 상세 문구로 구별하지 않습니다.

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

Unauthorized
상태사용 조건
400 Bad Request잘못된 경로·JSON·본문 길이 또는 허용하지 않는 쿼리
401 Unauthorized알 수 없는 어댑터, 잘못된 서명·claim, 만료되거나 재사용된 JWT
403 Forbidden저장소 또는 문서 접근 권한 없음
404 Not Found대상 항목이나 요청 경로 없음
405 Method Not Allowed알려진 작업 경로에 잘못된 메서드 사용(공개 예제)
409 Conflict이름·타입·잠금 소유자 충돌, 비어 있지 않은 디렉터리 삭제 거부
411 Length Required본문에 필요한 Content-Length 누락
413 Payload Too Large요청 본문, 문서, 목록 크기 또는 Provider가 설정한 제한 초과
415 Unsupported Media Type요청 Content-Type이 작업의 규격과 다름
500 Internal Server ErrorProvider가 처리하지 못한 내부 오류(공개 예제). 세부 예외는 응답에 노출하지 않음
501 Not Implemented위의 정확한 선택적 작업 미지원 선언
503 Service Unavailable일시적인 저장소 장애

인증 오류를 기능 미지원 응답으로 바꾸거나 저장소 장애를 성공으로 응답하지 않습니다. put 실패 시 기존 파일을 보존하고 임시 파일을 정리합니다. 크기를 초과한 요청이나 응답을 chunked 전송으로 바꿔 재시도하지 않습니다.

공개 예제는 JSON 작업의 요청 본문에 별도로 16 KiB 제한을 둡니다. 이는 info·list 응답의 5 MiB 제한과 다른 경계입니다. 배포별 제한은 예제 설정과 실행 방법에서 확인합니다.