Control-Plane 개요
개요
Open Echo Partner SDK의 control-plane은 방송 인프라(그룹·컨텐츠·템플릿·예약) 관리와 이력 조회를 담당합니다. 방송 실행(broadcast.*)과 달리 설정 데이터를 읽고 쓰는 API입니다.
이 문서는 control-plane 뿐 아니라 SDK 전체에 적용되는 capability·grant 권한 모델을 규범적으로 설명합니다. 다른 가이드 문서의 "앱 capability"·"참조 handle 조건" 서술은 이 문서의 정의를 전제합니다.
권한 모델
capability 종류
파트너 앱의 모든 API 호출은 capability(기능 단위 권한) 검사를 먼저 거칩니다. 앱에 해당 capability grant 가 없으면 CapabilityDenied(403)가 발생합니다. capability는 8종입니다.
| capability | 게이트하는 기능 |
|---|---|
TTS_EXECUTE | TTS 방송 실행 |
MIC_EXECUTE | MIC 방송 실행 |
DEVICE | 노출 디바이스 목록 조회 |
DEVICE_GROUP | 디바이스 그룹 CRUD |
CONTENT | 방송 컨텐츠 조회·생성·수정 |
TEMPLATE | 방송 템플릿 CRUD |
SCHEDULE | 예약 방송 CRUD |
HISTORY | 방송 이력 조회, 진행 구독 |
grant 액션 — MANAGE · READ · EXECUTE
DEVICE_GROUP·CONTENT·TEMPLATE·SCHEDULE·HISTORY·DEVICE capability는 세부 액션과 함께 부여됩니다.
- MANAGE — 리소스의 생성·수정·삭제와 소유권 기반 단건 조회를 포함한 관리.
groups(create/get/addMembers/removeMembers/delete/members)·templates(create/get/update/delete)·schedules(create/get/update/delete)가 MANAGE 로 게이트됩니다. - READ — 목록 조회, 그리고 노출·grant 범위 기반 조회.
groups.list/templates.list/schedules.list,contents.list/get,history.list/get,targets.list가 READ 로 게이트됩니다. (contents.get은 소유권이 아니라 handle 단위 READ grant로 게이트된다는 점에서, 소유권을 검증하는groups.get/templates.get/schedules.get(MANAGE)과 메커니즘이 다릅니다.) - EXECUTE — 특정 handle(컨텐츠·템플릿)을 방송에 실행으로 사용할 권한. handle 별로 별도 부여되는 grant 입니다.
handle 단위 EXECUTE grant
일부 호출은 capability 게이트를 통과해도, 요청에 담은 특정 handle 을 실행하려면 그 handle 에 대한 EXECUTE grant 가 추가로 필요합니다.
| 호출 | EXECUTE grant 가 필요한 handle |
|---|---|
oe.broadcast.tts.fromContent() | contentHandle — CONTENT EXECUTE |
oe.templates.create() | contentHandle — CONTENT EXECUTE |
oe.schedules.create() | templateHandle — TEMPLATE EXECUTE |
oe.schedules.update()(템플릿 교체 시) | 새 templateHandle — TEMPLATE EXECUTE |
주의:
MANAGEgrant 는EXECUTEgrant 를 자동으로 포함하지 않습니다. 앱이 직접 만든 컨텐츠·템플릿이라도 방송에 실행하려면 별도의EXECUTEgrant 가 필요할 수 있습니다. EXECUTE grant 가 없으면TargetUnresolved(404)로 응답합니다 — handle 이 존재하는지 자체를 노출하지 않습니다.
노출(exposure)과 위임(delegation)
파트너 앱이 참조할 수 있는 디바이스 handle 은 두 가지 방식으로 노출됩니다.
- 선별 노출 — 디바이스별로 개별 grant 를 받아
oe.targets.list()에 나타납니다. - 전체 위임 — 현장 단위로 위임을 받으면, 개별 grant 없이 그 현장에서 사용 가능한 디바이스 전체가 자동으로
oe.targets.list()에 나타납니다.
어느 방식으로 노출됐든 handle 은 방송 대상 지정(broadcast.*)·그룹 멤버 구성·템플릿 개별 디바이스 지정(deviceHandles)에 동일하게 사용할 수 있습니다. oe.targets.list()가 반환하는 목록은 조회 시점의 스냅샷입니다 — 그 사이 노출이 사라진 handle 을 나중에 사용하면 TargetUnresolved가 발생할 수 있습니다.
all-or-none 시맨틱
여러 handle 을 한 번에 지정하는 요청(deviceHandles 다중 지정, 그룹 멤버 추가·제거 등)은 all-or-none 으로 검증됩니다. 하나라도 해석할 수 없으면 요청 전체가 거부되며, 일부만 반영되는 경우는 없습니다.
참고: 방송 실행 시점의
partial옵션(대상 일부 제외 허용)은 이 handle 해석 단계와는 다른 층입니다. all-or-none 은 handle 이 유효한 참조인지를 검증하는 단계이고,partial은 유효한 대상 중 방송 시점에 사용할 수 없는 대상을 제외할지를 결정하는 단계입니다. 상세는 TTS 방송을 참조하세요.
소유권과 존재 은닉
그룹·템플릿·예약·자작 컨텐츠는 생성한 파트너 앱이 소유합니다. 다른 앱이 소유한 handle 을 조회·수정·삭제하려고 하면, 권한 부족을 알리는 대신 TargetUnresolved(404) 로 응답합니다 — 그 handle 이 존재하는지 자체를 노출하지 않기 위한 설계입니다.
노출 디바이스 조회 — oe.targets.list()
앱에 노출된 디바이스 목록을 조회합니다. 방송 대상 지정(broadcast.*)·그룹 멤버 구성·템플릿 개별 디바이스 지정의 소스입니다. 선별 노출(개별 grant)과 전체 위임(현장 단위) 두 방식 모두 이 목록에 함께 나타납니다(위 "노출과 위임" 절 참조).
기본 정보
| 메서드 | 앱 capability | 참조 handle 조건 | 반환 타입 |
|---|---|---|---|
oe.targets.list() | DEVICE | 없음 — capability 만으로 게이트되고, 이후 노출된(선별 노출 + 전체 위임) 디바이스 집합이 반환됨 | Promise<DeviceView[]> |
요청
입력 파라미터가 없습니다.
응답
| 이름 | 타입 | 설명 |
|---|---|---|
handle | string | 디바이스 handle |
name | string | 디바이스 이름 |
status | string | 디바이스 상태 |
online | boolean | undefined | 조회 시점의 온라인 여부를 나타내는 정보 라벨. 방송 시점의 실제 상태를 보장하지 않으므로 대상 선택 제한 근거로 사용하지 마세요 |
예제
const devices = await oe.targets.list();
for (const d of devices) {
console.log(d.handle, d.name, d.status);
}
대표 에러
| 에러 | HTTP 상태 | 원인 | 해결 방법 |
|---|---|---|---|
CapabilityDenied | 403 | 앱에 DEVICE capability grant 가 없음 | Open Echo 지원 채널로 grant 를 요청하세요 |
Capability 요약
| API | 필요 capability |
|---|---|
oe.targets.list() | DEVICE READ |
oe.groups.list() | DEVICE_GROUP READ |
oe.groups.create(), .get(), .addMembers(), .removeMembers(), .members(), .delete() | DEVICE_GROUP MANAGE |
oe.templates.list() | TEMPLATE READ |
oe.templates.create(), .get(), .update(), .delete() | TEMPLATE MANAGE(create()는 추가로 CONTENT EXECUTE(contentHandle) + DEVICE READ×N 또는 DEVICE_GROUP READ(대상 handle)) |
oe.schedules.list() | SCHEDULE READ |
oe.schedules.create(), .get(), .update(), .delete() | SCHEDULE MANAGE(create()/템플릿 교체 시 update()의 templateHandle은 추가로 TEMPLATE EXECUTE) |
oe.contents.list(), oe.contents.get() | CONTENT READ |
oe.contents.create(), oe.contents.update() | CONTENT MANAGE |
oe.history.list(), oe.history.get(), oe.broadcast.subscribe() | HISTORY READ |
oe.broadcast.tts.send() | TTS_EXECUTE |
oe.broadcast.mic.start() | MIC_EXECUTE |
oe.broadcast.tts.fromContent() | TTS_EXECUTE + CONTENT EXECUTE(contentHandle) |
oe.broadcast.tts.stop(), MicSession.stop() | 없음(세션 소유권만) |
oe.languages.list() | 없음(게이트 없음) |
capability가 없으면 각 API 호출에서 CapabilityDenied(403)가 발생합니다. Open Echo 지원 채널로 capability 부여를 요청하세요 (킷 루트 ONBOARDING.md).
SDK 비대칭 요약
파트너 SDK의 control-plane API는 운영자 콘솔과 기능이 동일하지 않습니다. 아래 표에서 제공하지 않는 작업을 확인하세요.
| 리소스 | list | get | create | update | delete | 기타 |
|---|---|---|---|---|---|---|
groups | ✓ | ✓ | ✓ | 없음 | ✓ | members, addMembers, removeMembers |
templates | ✓ | ✓ | ✓ | ✓ | ✓ | — |
schedules | ✓ | ✓ | ✓ | ✓ | ✓ | 템플릿 기반만 |
contents | ✓ | ✓ | ✓ | ✓ | 없음 | update는 add-only(언어 추가) |
history | ✓ | ✓ | — | — | — | read-only (단 get으로 상세 조회 가능 — 재방송 조합용) |
비대칭 주요 사항:
groups.update없음 — 그룹명 변경 미지원. 멤버십 편집(addMembers/removeMembers)만 가능.contents.delete없음 — 생성(create: 제목+한국어 원문+번역 언어)·수정(update: 제목 변경+언어 add-only)은 가능. 즉석 1회 방송은broadcast.tts.send(), 노출 컨텐츠 즉시 방송은broadcast.tts.fromContent().schedules는 템플릿 기반만 —CreateScheduleInput.templateHandle필수. 컨텐츠+대상 직접 예약 미노출.
목록 필터
네 개 목록 API는 서버측 필터 파라미터를 받습니다. 모든 텍스트 필터는 부분일치·대소문자 무시입니다.
| 리소스 | 메서드 | 필터 | 대상 필드 |
|---|---|---|---|
groups | list({ search }) | search | 그룹명(label) |
contents | list({ search }) | search | 제목(title) |
schedules | list({ search }) | search | 예약명(name) |
history | list({ type, search }) | type · search | type=방송 유형('MIC' | 'TTS') · search=컨텐츠 제목 |
templates.list()는 필터를 받지 않습니다(무인자). history의 type은 공개 계약값('MIC' / 'TTS')을 전달합니다 — 상세는 방송 이력의 HistoryView.type 설명을 참조하세요.
권장 사용 순서
방송 예약을 위해서는 다음 순서로 리소스를 준비합니다.
컨텐츠 확보 (contents.list / contents.create)
↓
그룹 생성 + 멤버 편집 (groups.create → addMembers)
↓
템플릿 생성 (templates.create — contentHandle + groupHandle)
↓
예약 생성 (schedules.create — templateHandle 필수)
자주 만나는 상황
권한 없음 → CapabilityDenied
import { CapabilityDenied } from "@openecho/partner-sdk";
try {
await oe.schedules.list();
} catch (err) {
if (err instanceof CapabilityDenied) {
// SCHEDULE capability가 없음 — Open Echo 지원 채널로 grant 부여 요청
}
}
대상 없음 → TargetUnresolved
템플릿에 지정된 그룹에 ASSIGNED 상태 디바이스가 없으면 방송 시 TargetUnresolved 에러가 발생합니다. 그룹에 활성 디바이스(ASSIGNED)를 추가하세요.
빈 목록
list() 결과가 빈 배열([])인 것은 정상입니다. 아직 생성된 리소스가 없음을 의미합니다.
const groups = await oe.groups.list();
if (groups.length === 0) {
// 그룹이 아직 없음 — 생성 필요
}
게이트웨이 마스킹 주의
게이트웨이/프록시가 API 4xx 응답을 HTML(200)로 치환하면, SDK는 GatewayMasked(HTML 본문)·InvalidApiResponse(비-JSON 본문) 에러를 직접 throw 합니다. 목록 API 를 여러 개 동시에 호출하는 화면에서는 Promise.allSettled로 한 자원의 실패가 나머지를 무너뜨리지 않게 하는 것을 권장합니다. 진단 절차는 에러 처리를 참조하세요.
참고 코드
레퍼런스 앱의 그룹·컨텐츠·템플릿·예약·이력 화면 구현은 각 기능별 가이드의 "참고 코드" 절에서 파일 단위로 안내합니다.
각 리소스 상세
- 예약 방송 —
oe.schedules.*CRUD, recurrence별 조건부 필드 - 방송 템플릿 —
oe.templates.*CRUD - 방송 컨텐츠 —
oe.contents.*조회·생성·수정(add-only) - 디바이스 그룹 —
oe.groups.*create/멤버십/delete, update 없음 상세 - 방송 이력 —
oe.history.*read-only, Page 페이지네이션,get재방송 조합

