---
tags: [Slack, Canvas, API, Robeing, Integration, Guide]
date: 2025-09-15
modified: 2025-09-15
---

# 로빙(Roboing)을 위한 Slack Canvas 연동 기술 가이드

## 1. 개요: Slack Canvas와 API의 이해

이 문서는 로빙이 Slack Canvas를 프로그래매틱하게 생성, 편집, 조회 및 공유하는 방법에 대한 기술적 절차와 제약 사항을 정리합니다. Slack의 공식 API 문서를 기반으로, 실제 구현에 필요한 모든 단계를 구체적으로 제시합니다.

### 1.1. Slack Canvas란?

Slack Canvas는 채널이나 대화에 정보를 영구적으로 고정할 수 있는 문서 기능입니다. API 관점에서 두 가지 주요 유형이 있습니다.

-   **독립 캔버스 (Standalone Canvas)**: 특정 채널에 종속되지 않는 독립적인 문서입니다. 프로젝트 위키, 개인 메모 등으로 활용할 수 있습니다.
-   **채널 캔버스 (Channel-attached Canvas)**: 특정 채널의 탭에 고정되는 문서입니다. 해당 채널의 온보딩 가이드, FAQ, 공지사항 등으로 사용하기에 적합합니다.

### 1.2. 핵심 API 엔드포인트

캔버스 조작은 주로 다음의 Web API를 통해 이루어집니다.

-   `canvases.create`: 새 캔버스를 생성합니다. (독립 또는 채널 탭)
-   `canvases.edit`: 캔버스의 내용을 수정합니다. (섹션 단위 삽입, 교체, 삭제)
-   `canvases.sections.lookup`: 특정 텍스트를 포함한 섹션(헤더 등)의 ID를 조회하여 편집 위치를 특정합니다.
-   `canvases.access.set` / `canvases.access.delete`: 캔버스에 대한 사용자 또는 채널 단위의 접근 권한을 설정하거나 삭제합니다.
-   `files.list`: `types=canvas` 파라미터를 사용하여 워크스페이스 내의 캔버스 목록을 조회합니다.
-   `files.info`: 개별 캔버스 파일의 메타데이터와 다운로드 URL 등을 조회합니다.

### 1.3. 콘텐츠 포맷: 캔버스 전용 마크다운

**매우 중요한 제약 사항**으로, 캔버스 API는 Slack 메시지에 사용되는 `Block Kit`을 지원하지 않습니다. 모든 콘텐츠는 `document_content` 객체 내에 `type: "markdown"`과 `markdown: "..."` 형태로 전달해야 합니다.

또한, 캔버스에서 사용되는 마크다운은 일반 마크다운과 약간의 차이가 있습니다.

-   **사용자 멘션**: `![](@U0123ABCDE)`
-   **채널 멘션**: `![](#C0567EFGHI)`
-   **지원 요소**: 헤딩 (`#`, `##`), 목록, 체크리스트, 테이블, 인용구 등을 지원합니다.

### 1.4. 인증: 필수 OAuth 스코프

로빙의 매니페스트 파일에 다음 스코프들이 포함되어야 합니다. 앱 재설치가 필요할 수 있습니다.

-   **필수 스코프**:
    -   `canvases:write`: 캔버스 생성, 편집, 삭제, 공유
    -   `canvases:read`: 캔버스 섹션 조회 등 읽기 관련 작업
    -   `files:read`: 캔버스 목록 조회 및 콘텐츠 다운로드
-   **선택적 스코프**:
    -   `conversations:read`: 채널 ID 탐색 및 정보 조회
    -   `chat:write`: 캔버스 생성/수정 후 안내 메시지 전송

---

## 2. 앱 매니페스트 설정 예시

로빙의 `slack.app.yml` 또는 매니페스트 설정에 아래와 같이 스코프를 정의합니다.

```yaml
_display_information:
  name: RO-BEING
features:
  bot_user:
    display_name: RO-BEING
    always_online: true
oauth_config:
  scopes:
    bot:
      # Canvas 필수 스코프
      - canvases:write
      - canvases:read
      - files:read

      # 선택적 스코프 (기능 확장을 위해 권장)
      - conversations:read
      - chat:write
      - users:read # 사용자 정보 조회용
settings:
  interactivity:
    is_enabled: true
  org_deploy_enabled: false
  socket_mode_enabled: true
  token_rotation_enabled: false
```

---

## 3. 핵심 구현 시나리오

### 3.1. 시나리오 1: 프로젝트 위키 (독립 캔버스)

**목표**: 새로운 프로젝트가 시작될 때, 관련 정보를 담은 독립적인 위키 캔버스를 생성하고, 내용을 지속적으로 갱신하며, 관련자에게 공유합니다.

**구현 흐름**:

1.  **생성**: `canvases.create`를 호출하여 캔버스를 생성합니다. `title`과 초기 `document_content` (markdown)를 전달합니다. 응답으로 받은 `canvas_id`를 로빙의 내부 DB에 저장하여 관리합니다.

2.  **편집 (내용 추가/수정)**:
    -   **문서 끝에 추가**: `canvases.edit`의 `changes` 배열에 `{ "operation": "insert_at_end", "document_content": ... }` 객체를 담아 호출합니다.
    -   **특정 섹션 수정**: `canvases.sections.lookup`을 사용하여 `## Action Items`와 같은 헤더 텍스트로 `section_id`를 찾습니다. 그 후 `canvases.edit`의 `changes` 배열에 `{ "operation": "replace", "section_id": "...", "document_content": ... }` 형태로 호출하여 해당 섹션만 교체합니다.

3.  **공유**: `canvases.access.set`을 호출하여 접근 권한을 부여합니다.
    -   **채널 공유**: `channel_ids` 파라미터에 공유할 채널 ID 목록을 전달합니다.
    -   **사용자 공유**: `user_ids` 파라미터에 사용자 ID 목록을 전달합니다. **(주의: 이 API가 성공하려면, 해당 사용자에게 사전에 캔버스 링크가 한 번 이상 공유된 적이 있어야 합니다.)**

4.  **탐색 및 조회**: `files.list` API에 `types=canvas` 쿼리를 사용하여 워크스페이스 내의 모든 캔버스를 검색합니다. 특정 캔버스의 `file_id`를 얻은 후, `files.info`로 상세 메타데이터를 조회하고, 응답에 포함된 `url_private`에 `Authorization: Bearer <TOKEN>` 헤더를 포함하여 요청하면 캔버스의 원본 콘텐츠를 다운로드할 수 있습니다.

### 3.2. 시나리오 2: 채널 온보딩/FAQ 자동화

**목표**: 새로운 채널이 생성되거나 로빙이 채널에 초대될 때, 해당 채널의 캔버스 탭에 온보딩 가이드와 FAQ를 자동으로 생성하고 주기적으로 업데이트합니다.

**구현 흐름**:

1.  **채널 식별**: `app_home_opened` 이벤트나 채널 생성 관련 이벤트를 통해 `channel_id`를 확보합니다. (`conversations:read` 스코프 필요)

2.  **채널 탭에 캔버스 생성**: `canvases.create` 호출 시, `channel_id` 파라미터를 함께 전달합니다. 이렇게 하면 생성된 캔버스가 자동으로 해당 채널의 캔버스 탭에 고정됩니다.

3.  **접근 제어**: 채널에 연결된 캔버스는 기본적으로 채널 멤버들의 권한을 따릅니다. 추가적인 권한 설정이 필요하면 `canvases.access.set`을 사용합니다. **(주의: DM이나 다인 DM 채널에는 `channel_id`로 권한을 설정할 수 없습니다.)**

4.  **주기적 갱신**: FAQ 내용이 변경될 경우, `canvases.sections.lookup`으로 `## 자주 묻는 질문`과 같은 특정 헤더를 찾아 해당 섹션만 `canvases.edit`으로 교체하여 전체 문서를 덮어쓰지 않도록 합니다.

### 3.3. 시나리오 3: 회의록/인시던트 문서화

**목표**: 회의나 인시던트 대응이 끝난 후, 관련 대화 내용을 요약하여 캔버스 문서의 정해진 서식(템플릿)에 맞게 구조화하여 기록합니다. (예: Action Item은 체크리스트로, 담당자는 멘션으로)

**구현 흐름**:

1.  **템플릿 캔버스 준비**: 미리 약속된 헤더(`## 요약`, `## 결정 사항`, `## Action Items`)를 가진 템플릿 캔버스를 준비하거나, 생성 시 동적으로 만듭니다.

2.  **콘텐츠 구조화**: LLM을 통해 요약된 내용을 캔버스 전용 마크다운으로 변환합니다.
    -   Action Item: `- [ ] 내용 (@담당자)`
    -   테이블: `| 제목 | 내용 |
|---|---|`
    -   담당자/채널 멘션: `![](@U0123ABCDE)`, `![](#C0567EFGHI)`

3.  **섹션별 내용 삽입**: `canvases.sections.lookup`으로 각 헤더의 `section_id`를 찾은 뒤, `canvases.edit`의 `insert_after` operation을 사용하여 각 섹션 아래에 구조화된 콘텐츠를 삽입합니다.

4.  **이미지/파일 첨부**: 스크린샷이나 관련 파일을 캔버스에 첨부해야 할 경우:
    -   먼저 해당 파일을 Slack에 업로드합니다.
    -   `files.info` API로 업로드된 파일의 `permalink`를 조회합니다.
    -   이 `permalink`를 사용하여 `![이미지 설명](<permalink>)` 형태의 마크다운 문자열을 생성하여 캔버스에 삽입합니다.

5.  **완료 알림**: 작업이 완료되면 `chat.postMessage`를 통해 관련 채널에 캔버스 링크와 함께 완료 사실을 알립니다.

---

## 4. 중요 제약 조건 및 권장 사항

-   **유료 플랜 전용**: Canvas API는 Slack의 유료 플랜에서만 사용 가능합니다. 무료 플랜에서는 API 호출이 실패합니다.
-   **DM/MPDM 공유 제약**: `canvases.access.set`에서 `channel_ids`를 사용하여 DM이나 다인 DM(MPDM)에 권한을 부여할 수 없습니다. 반드시 `user_ids`를 사용해야 합니다.
-   **마크다운 변환 필수**: 기존 메시지 `Block Kit` JSON을 캔버스에 직접 사용할 수 없습니다. 로빙이 생성하는 모든 콘텐츠는 캔버스 전용 마크다운으로 변환하는 로직이 반드시 필요합니다.
-   **이미지 첨부**: 이미지는 외부 공개 URL을 사용하거나, Slack에 먼저 업로드한 후 `permalink`를 통해 참조해야 합니다. Base64 인코딩 방식 등은 지원되지 않습니다.
-   **데이터 보관 정책**: 로빙이 캔버스 내용을 외부 DB에 장기 보관하거나 인덱싱할 경우, Slack의 데이터 보관 및 사용 정책을 준수해야 합니다. 민감 정보 처리 시 특히 주의가 필요합니다.

## 5. 검증 결과 (2025-03)

**채널 탭 Canvas 읽기**: `files:read`만으로 가능함.

- 채널: C0ALCV5S0FL (ir컨설팅서비스-및-랜딩페이지)
- Canvas "온보딩 회의": `files.list(types=canvas, channel=...)` → `files.info` → `url_private` GET으로 본문(HTML) 다운로드 가능
- canvas_id: F0AL6L34CV9
- `canvases.sections.lookup` / `canvases.edit`은 `canvases:read`/`canvases:write` 스코프 추가 필요 (현재 Company-X 봇에 없음)
- 검증 스크립트: `rb8001/scripts/test_canvas_access.py`

## 6. 빠른 구현을 위한 체크리스트

1.  **[ ]** 로빙의 Slack 앱 매니페스트에 `canvases:write`, `canvases:read`, `files:read` 스코프를 추가하고 앱을 재설치합니다.
2.  **[ ]** `canvases.create`를 호출하여 첫 캔버스를 생성하고, 반환된 `canvas_id`를 저장하는 로직을 구현합니다.
3.  **[ ]** `canvases.sections.lookup`과 `canvases.edit`을 조합하여, 특정 헤더를 기준으로 내용을 수정하는 기능을 구현합니다.
4.  **[ ]** `canvases.access.set`을 사용하여 생성된 캔버스를 특정 채널이나 사용자에게 공유하는 기능을 구현하고, DM/MPDM의 제약사항을 코드 레벨에서 처리합니다.
5.  **[ ]** `files.list` (`types=canvas`)를 통해 캔버스를 검색하는 기능을 구현합니다.
6.  **[ ]** 사용자/채널 멘션을 캔버스 전용 마크다운(`![](@U...)`, `![](#C...)`)으로 변환하는 유틸리티 함수를 작성합니다.
