API

외부 시스템이 SmartOffice Online 편집기로 문서를 여는 방식은 WOPI(Web Application Open Platform Interface) 입니다. 편집기는 파일을 직접 갖고 있지 않고, 여러분의 시스템(WOPI 호스트)에게 파일을 달라고 하고 저장할 때 돌려줍니다.

flow문서 하나를 여는 동안 오가는 순서
  1. 내 시스템 — 사용자가 문서를 누르면, 그 문서에 대한 WOPISrc(파일 주소)와 access_token(일회용 열쇠)을 만듭니다.
  2. 내 시스템 → 브라우저 — 편집기 주소에 그 둘을 붙여 <iframe> 으로 띄웁니다.
  3. 편집기 → 내 시스템CheckFileInfo 로 파일 정보(이름·크기·권한)를 묻습니다.
  4. 편집기 → 내 시스템GetFile 로 파일 내용을 받아 엽니다.
  5. 편집기 → 내 시스템 — 저장할 때 PutFile 로 바뀐 내용을 보냅니다.
  ┌───────────┐   ① 편집기 URL(WOPISrc + token)   ┌────────────┐
  │  내 시스템 │ ────────────────────────────────▶ │  브라우저   │
  │  (WOPI    │                                    │  (iframe)  │
  │   호스트)  │ ◀───────────────────────────────  └─────┬──────┘
  └─────┬─────┘   ③ CheckFileInfo (파일 정보)            │
        │         ④ GetFile      (파일 내용)             │ ② 편집기 로드
        │         ⑤ PutFile      (저장)                  ▼
        │                                        ┌──────────────┐
        └───────────────────────────────────────▶│ office.smart │
                     서버 ↔ 서버 (브라우저 아님)   └──────────────┘

③④⑤ 는 편집기 서버가 내 시스템으로 직접 부르는 요청입니다. 브라우저를 거치지 않으므로, 내 시스템의 WOPI 주소가 편집기 서버에서 닿는 곳이어야 합니다(사설망·localhost 주의).

endpoint내 시스템이 만들어야 하는 것 · 편집기가 주는 것
누가경로하는 일
편집기 제공GET /hosting/discovery확장자별 편집기 진입 URL 목록(XML)
편집기 제공GET /hosting/capabilities제품 이름·버전·기능(JSON)
편집기 제공POST /cool/convert-to/<형식>WOPI 없이 파일 변환
내가 구현GET /wopi/files/<id>CheckFileInfo — 파일 정보
내가 구현GET /wopi/files/<id>/contentsGetFile — 파일 내용
내가 구현POST /wopi/files/<id>/contentsPutFile — 저장

편집기가 어떤 확장자를 어떤 URL 로 여는지 알려 주는 목록입니다. 연동을 시작할 때 한 번 받아 캐시해 두고, 편집기를 올릴 때마다 갱신하면 됩니다(빌드 해시가 URL 에 들어가므로 버전이 바뀌면 값도 바뀝니다).

GET /hosting/discovery
curl -s https://office.smartertools.co.kr/hosting/discovery

응답(발췌) — urlsrc 가 편집기 진입 주소입니다.

<wopi-discovery>
  <net-zone name="external-https">
    <app name="application/vnd.openxmlformats-officedocument.wordprocessingml.document">
      <action ext="docx" name="edit"
              urlsrc="https://office.smartertools.co.kr/browser/<해시>/cool.html?"/>
    </app>
    <app name="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
      <action ext="xlsx" name="edit" urlsrc="…"/>
    </app>
  </net-zone>
</wopi-discovery>
GET /hosting/capabilities제품 이름·버전 확인용
curl -s https://office.smartertools.co.kr/hosting/capabilities
{
  "productName": "SmartOffice Online",
  "productVersion": "25.04.10.3",
  "convert-to": { "available": true, "endpoint": "/cool/convert-to" },
  "hasMobileSupport": true,
  "hasTemplateSource": true
}

내 시스템이 만들어야 하는 세 개의 엔드포인트입니다. 편집기는 요청마다 access_token 을 쿼리(또는 Authorization: Bearer)로 함께 보냅니다 — 그 값으로 누가 어떤 파일에 접근하는지 확인하세요.

1. CheckFileInfoGET /wopi/files/<id>
{
  "BaseFileName":        "보고서.docx",        // 확장자 포함 (편집기가 이걸로 형식을 정함)
  "Size":                20480,                // 바이트
  "OwnerId":             "user-1",
  "UserId":              "user-1",
  "UserFriendlyName":    "홍길동",              // 편집 중 표시되는 이름
  "UserCanWrite":        true,                 // false 면 읽기 전용으로 열림
  "UserCanNotWriteRelative": true,             // "다른 이름으로 저장" 안 쓸 때
  "LastModifiedTime":    "2026-09-05T06:00:00.000Z",
  "Version":             "v3",                 // 내용이 바뀌면 반드시 함께 바뀌어야 함
  "PostMessageOrigin":   "https://my-app.example.com"  // 편집기→내 페이지 메시지 허용 출처
}

흔한 실수BaseFileName 에 확장자가 없으면 편집기가 형식을 몰라 열지 못합니다. Version 을 고정값으로 두면 저장 후 편집기가 옛 내용을 계속 씁니다.

2. GetFileGET /wopi/files/<id>/contents

파일 바이트를 그대로 돌려줍니다(application/octet-stream). JSON 으로 감싸면 안 됩니다.

3. PutFilePOST /wopi/files/<id>/contents

본문이 저장할 파일 바이트입니다. 성공하면 200 과 함께 새 {"LastModifiedTime": …} 를 돌려주면 됩니다.

exampleNode.js(Express) 최소 구현 — 이대로도 편집·저장이 됩니다
const express = require('express');
const fs = require('fs');
const app = express();

const FILE = '/srv/docs/보고서.docx';
const TOKEN = 'demo-token';                     // 실제로는 사용자·문서별 일회용 토큰

// 편집기가 보내는 토큰 확인 (쿼리 또는 Authorization 헤더)
function auth(req, res, next) {
  const t = req.query.access_token ||
            (req.headers.authorization || '').replace(/^Bearer /, '');
  if (t !== TOKEN) return res.sendStatus(401);
  next();
}

// 1) CheckFileInfo
app.get('/wopi/files/:id', auth, (req, res) => {
  const st = fs.statSync(FILE);
  res.json({
    BaseFileName: '보고서.docx',
    Size: st.size,
    OwnerId: 'user-1',
    UserId: 'user-1',
    UserFriendlyName: '홍길동',
    UserCanWrite: true,
    UserCanNotWriteRelative: true,
    LastModifiedTime: st.mtime.toISOString(),
    Version: String(st.mtimeMs),               // 저장할 때마다 바뀌어야 한다
  });
});

// 2) GetFile
app.get('/wopi/files/:id/contents', auth, (req, res) => {
  res.type('application/octet-stream');
  fs.createReadStream(FILE).pipe(res);
});

// 3) PutFile
app.post('/wopi/files/:id/contents', auth,
  express.raw({ type: '*/*', limit: '100mb' }), (req, res) => {
    fs.writeFileSync(FILE, req.body);
    res.json({ LastModifiedTime: new Date().toISOString() });
  });

app.listen(3030);

편집기는 <iframe> 으로 띄웁니다. 주소에 WOPISrc(내 WOPI 파일 주소, URL 인코딩)와 access_token 을 붙입니다.

URL 형식
https://office.smartertools.co.kr/browser/<해시>/cool.html
    ?WOPISrc=https%3A%2F%2Fmy-app.example.com%2Fwopi%2Ffiles%2F123
    &access_token=demo-token
    &lang=ko-KR
    &ui_lang=ko-KR

<해시>Discoveryurlsrc 에서 가져오거나 /hosting/capabilitiesproductVersionHash 를 씁니다.

example내 페이지에 붙이는 코드
<iframe id="office" style="width:100%;height:100vh;border:0"
        allow="clipboard-read *; clipboard-write *"></iframe>

<script>
(async () => {
  const OFFICE  = 'https://office.smartertools.co.kr';
  const WOPISRC = 'https://my-app.example.com/wopi/files/123';
  const TOKEN   = 'demo-token';

  // 편집기 빌드 해시를 받아 URL 을 만든다
  const caps = await fetch(OFFICE + '/hosting/capabilities').then(r => r.json());
  const url = OFFICE + '/browser/' + caps.productVersionHash + '/cool.html'
            + '?WOPISrc=' + encodeURIComponent(WOPISRC)
            + '&access_token=' + encodeURIComponent(TOKEN)
            + '&lang=ko-KR&ui_lang=ko-KR';
  document.getElementById('office').src = url;
})();
</script>

토큰이 길거나 URL 에 남기고 싶지 않으면 <form method="POST">access_token 을 본문에 담아 iframe 을 target 으로 보내는 방식도 지원합니다.

PostMessage편집기 ↔ 내 페이지 신호

CheckFileInfoPostMessageOrigin 을 넣으면 편집기가 내 페이지로 상태를 보냅니다 (문서 로드 완료, 수정됨, 저장됨 등). 저장 버튼을 내 UI 에 두고 싶을 때 씁니다.

window.addEventListener('message', (e) => {
  if (e.origin !== 'https://office.smartertools.co.kr') return;      // 출처 확인은 필수
  const msg = JSON.parse(e.data);
  if (msg.MessageId === 'App_LoadingStatus' &&
      msg.Values.Status === 'Document_Loaded') { /* 로드 완료 */ }
  if (msg.MessageId === 'Doc_ModifiedStatus') { /* 수정 여부 */ }
});

// 내 UI 에서 저장시키기
document.getElementById('office').contentWindow.postMessage(
  JSON.stringify({ MessageId: 'Action_Save', Values: { Notify: true } }),
  'https://office.smartertools.co.kr');

문서를 편집하지 않고 변환만 할 때는 WOPI 호스트가 필요 없습니다. 파일을 올리면 변환된 파일이 바로 돌아옵니다.

POST /cool/convert-to/<형식>
# docx → pdf
curl -F "data=@보고서.docx" \
     https://office.smartertools.co.kr/cool/convert-to/pdf \
     -o 보고서.pdf

# xlsx → csv, pptx → pdf, docx → odt … 형식만 바꾸면 된다
curl -F "data=@표.xlsx" https://office.smartertools.co.kr/cool/convert-to/csv -o 표.csv
// Node.js
const fd = new FormData();
fd.append('data', new Blob([buf]), '보고서.docx');
const pdf = await fetch('https://office.smartertools.co.kr/cool/convert-to/pdf', {
  method: 'POST', body: fd,
}).then(r => r.arrayBuffer());

변환도 허용된 호스트에서만 받습니다(아래 «필요한 설정» 참고). 큰 파일은 시간이 걸리니 앞단 프록시의 타임아웃을 넉넉히 주세요.

연동하려면 편집기 쪽에도 설정이 필요합니다. 아래 항목이 안 맞으면 문서가 열리지 않거나 «Failed to load document» 로 끝납니다.

coolwsd.xml편집기 서버에서 설정
  1. WOPI 호스트 허용 — 내 시스템 주소를 storage > wopi 목록에 넣습니다. 여기 없는 곳에서 온 WOPISrc 는 거부됩니다.
    <storage>
      <wopi desc="WOPI settings">
        <alias_groups mode="groups">
          <group>
            <host allow="true">https://my-app\.example\.com</host>
          </group>
        </alias_groups>
      </wopi>
    </storage>
    값은 정규식입니다 — 점은 \. 로 escape 하세요.
  2. iframe 허용 — 편집기를 내 페이지에 끼워 넣으려면 내 출처를 허용해야 합니다. net > frame_ancestorshttps://my-app.example.com 을 넣습니다. (스킴을 빼면 https 로만 해석되니 http 로 접속한다면 http://… 도 함께.)
  3. 앞단 프록시 — nginx 등이 있으면 /cool 에 웹소켓 업그레이드 (Upgrade·Connection)를 넘기고 proxy_read_timeout 을 넉넉히(예 36000s), client_max_body_size 를 파일 크기에 맞게 올리세요.
  4. 서버끼리 닿기 — 편집기 서버가 내 WOPI 주소로 직접 요청합니다. localhost·사설 IP 를 WOPISrc 에 쓰면 편집기가 못 찾습니다.
  5. 바꾼 뒤 재시작 — coolwsd 는 설정을 뜰 때 한 번만 읽습니다. 이 콘솔의 관리콘솔 > 정보 탭에서 재시작할 수 있습니다.
현재 편집기
공개 주소https://office.smartertools.co.kr
콘솔이 쓰는 주소http://smoffice-orig:9980
Discoveryhttps://office.smartertools.co.kr/hosting/discovery
Capabilitieshttps://office.smartertools.co.kr/hosting/capabilities