콘텐츠로 이동

워크스페이스·credential·사용자 그룹 API

워크스페이스는 회원, API key, 시나리오와 대화를 격리하는 테넌트 경계입니다. 이 문서에서 헤더는 X-Workspace-Id: <workspace-uuid>를 뜻합니다. 인증·테넌트 해석의 상세는 워크스페이스와 테넌시, credential 수명주기는 API key와 client-secret, 공통 타입은 스키마를 참고하십시오.

Credential 구분

Credential 발급 권한 소비자 목적
access JWT 로그인 성공 App Backend 로그인 회원 REST 인증
워크스페이스 API key 워크스페이스 OWNER App Backend의 허용된 GET, Socket Backend 서버 간 읽기와 실시간 세션 인증
Ed25519 client-secret ACTIVE 멤버 Socket Backend 단기 실시간 세션 인증

API key와 client-secret은 이름이 비슷해도 서로 대체할 수 없습니다. client-secret은 App Backend REST Bearer credential이 아닙니다. API key는 App Backend의 제한된 조회와 Socket Backend 세션에 사용할 수 있지만, 브라우저에는 장기 API key 대신 client-secret을 전달해야 합니다.

워크스페이스

POST /v1/workspaces

  • 인증: access JWT
  • 테넌트: 부트스트랩 경로이므로 헤더 불필요
  • 성공: 200 OK
  • 부수 효과: 워크스페이스를 생성하고 요청자를 owner이자 OWNER/ACTIVE 멤버로 등록합니다. LLM provider는 서버 정책값으로 설정됩니다.
필드 필수 제약
name 예 공백 불가, 최대 128자
sttProvider 예 대소문자까지 WHISPER 또는 MAGO
{
  "name": "우리 병원",
  "sttProvider": "WHISPER"
}
{
  "status": 200,
  "message": "success"
}
  • 200: 생성과 OWNER 등록 성공
  • 400: 본문, 이름 또는 provider 검증 실패
  • 401: access JWT 누락·오류
  • 409: DB 유일성 충돌 등 무결성 충돌

GET /v1/workspaces

  • 인증: access JWT
  • 테넌트: 헤더 불필요
  • query: page 기본 1, size 기본 20
  • 성공: 200 OK
  • 부수 효과: 없음

요청자의 ACTIVE 멤버십이 있는 워크스페이스만 최신 생성순으로 조회합니다.

{
  "status": 200,
  "message": "success",
  "page": {
    "page": 1,
    "size": 20,
    "totalItems": 1,
    "totalPages": 1,
    "hasNext": false,
    "hasPrevious": false,
    "first": true,
    "last": true
  },
  "count": 1,
  "list": [
    {
      "id": "01900000-0000-7000-8000-000000000001",
      "workspaceName": "우리 병원",
      "createdAt": "2026-09-23T10:00:00"
    }
  ]
}
  • 200: 목록 또는 빈 목록
  • 401: 인증 오류

초대, 역할 변경, 소유권 이전 API는 기준 커밋에 없습니다.

워크스페이스 API key

세 경로 모두 access JWT, 헤더, 해당 워크스페이스의 ACTIVE 멤버십과 OWNER 역할이 필요합니다. API key 자체로 API key를 관리할 수 없습니다.

POST /v1/workspaces/api-keys

  • 성공: 200 OK
  • 부수 효과: 랜덤 원본 키를 생성하고 DB에는 SHA-256 해시와 표시용 접두만 저장
필드 필수 제약
name 예 공백 불가, 최대 128자
{
  "name": "분석 조회 서버"
}
{
  "status": 200,
  "message": "success",
  "data": {
    "id": "01900000-0000-7000-8000-000000000101",
    "name": "분석 조회 서버",
    "key": "<new-workspace-api-key>",
    "keyPrefix": "<display-prefix>",
    "keyType": "TEST",
    "createdAt": "2026-09-23T10:05:00"
  }
}

key는 이 응답에서만 한 번 확인할 수 있습니다. 예시는 실제 접두나 키 값을 제시하지 않으며, 운영·개발 프로파일에 따라 keyType과 접두 정책이 달라질 수 있습니다.

  • 200: 발급 성공
  • 400: 헤더 누락·형식 오류, 본문 또는 이름 검증 실패
  • 401: access JWT 오류
  • 403: 비멤버 또는 OWNER 아님
  • 404: 워크스페이스 없음

GET /v1/workspaces/api-keys

  • 요청 query·본문: 없음
  • 성공: 200 OK
  • 부수 효과: 없음
{
  "status": 200,
  "message": "success",
  "data": [
    {
      "id": "01900000-0000-7000-8000-000000000101",
      "keyName": "분석 조회 서버",
      "keyPrefix": "<display-prefix>",
      "keyType": "TEST",
      "keyStatus": "ACTIVE",
      "lastUsedAt": null,
      "createdAt": "2026-09-23T10:05:00"
    }
  ]
}

원본 키와 secretHash는 반환하지 않습니다. 폐기된 키도 감사 목적으로 남으며 상태는 ACTIVE, REVOKED, ROTATED 중 저장된 값을 그대로 보여 줍니다.

  • 200: 목록 반환
  • 400: 헤더 누락·형식 오류
  • 401: 인증 오류
  • 403: 비멤버 또는 OWNER 아님
  • 404: 워크스페이스 없음

DELETE /v1/workspaces/api-keys/{keyId}

  • path keyId: API key 레코드 UUID
  • 성공: 200 OK
  • 부수 효과: 물리 삭제하지 않고 keyStatus=REVOKED로 전환. 재호출은 멱등 성공
{
  "status": 200,
  "message": "success"
}
  • 200: 폐기 완료 또는 이미 폐기됨
  • 400: 헤더나 UUID 형식 오류
  • 401: 인증 오류
  • 403: 비멤버 또는 OWNER 아님
  • 404: 워크스페이스가 없거나 keyId가 대상 워크스페이스에 속하지 않음

API key 사용 시에는 다음처럼 전달합니다.

Authorization: Bearer <workspace-api-key>

허용 범위는 SCOPE_WORKSPACE_READ이며 회원 access JWT 역할을 대신하지 않습니다.

Ed25519 client-secret

POST /v1/workspaces/client-secrets

  • 인증: access JWT만 사용
  • 테넌트: 헤더 필수
  • 권한: 해당 워크스페이스의 ACTIVE 멤버; OWNER일 필요 없음
  • 본문: 전체 생략 가능
  • 성공: 201 Created
  • 부수 효과: Ed25519 개인키로 단기 JWT를 서명. DB 저장, jti 소비 기록, Socket Backend 호출은 하지 않음
필드 필수 제약·동작
expiresInSeconds 아니요 정수. 생략 시 서버 설정값, 300 미만은 300, 10,800 초과는 10,800으로 clamp
{
  "expiresInSeconds": 1800
}
{
  "status": 200,
  "message": "success",
  "data": {
    "clientSecret": {
      "value": "<ed25519-client-secret-jwt>",
      "expiresInSeconds": 1800,
      "expiresAt": "2026-09-23T01:30:00Z"
    }
  }
}
  • 201: 발급 성공. body의 공통 status는 200
  • 400: 헤더 누락·형식 오류 또는 역직렬화할 수 없는 본문
  • 401: access JWT 오류
  • 403: 대상 워크스페이스의 ACTIVE 멤버가 아님

토큰 header는 alg=EdDSA, typ=JWT이고 claim은 iss, 단일 문자열 aud, sub, workspaceId, jti, iat, exp입니다. apiKeyId는 없습니다. 상세 값과 의미는 스키마에 있습니다.

응답 1회 전달과 1회 사용은 다릅니다

client-secret은 응답에서만 전달되고 서버에 저장되지 않지만 일회용 토큰은 아닙니다. 유효 기간 안에는 재사용할 수 있습니다. access JWT의 HMAC 키와 분리된 Ed25519 서명키를 사용하며, Socket Backend는 공개키로 검증합니다.

사용자 그룹

모든 사용자 그룹 경로는 access JWT, 헤더, 대상 워크스페이스의 ACTIVE OWNER가 필요합니다. API key로 관리할 수 없습니다.

POST /v1/end-user-groups

  • 성공: 201 Created
  • 부수 효과: 워크스페이스 범위 사용자 그룹 생성
필드 필수 제약
name 예 공백 불가, trim 후 최대 128자; 제어·format·줄 구분·private-use 등 금지 문자 불가
{
  "name": "2026년 상담 참여자"
}
{
  "status": 200,
  "message": "success",
  "data": {
    "id": "01900000-0000-7000-8000-000000000201",
    "name": "2026년 상담 참여자",
    "createdAt": "2026-09-23T10:10:00"
  }
}
  • 201: 생성 성공
  • 400: 헤더 또는 이름 검증 실패
  • 401: 인증 오류
  • 403: OWNER 아님
  • 409: 같은 워크스페이스의 활성 그룹 이름 중복

GET /v1/end-user-groups

  • 요청 query·본문: 없음
  • 성공: 200 OK
  • 부수 효과: 없음
{
  "status": 200,
  "message": "success",
  "data": [
    {
      "id": "01900000-0000-7000-8000-000000000201",
      "name": "2026년 상담 참여자",
      "createdAt": "2026-09-23T10:10:00"
    }
  ]
}
  • 200: 목록 또는 빈 배열
  • 400/401/403: 헤더, 인증 또는 OWNER 검증 실패

POST /v1/end-user-groups/{groupId}/members

  • path groupId: 현재 워크스페이스의 사용자 그룹 UUID
  • 성공: 200 OK
  • 부수 효과: 외부 사용자 ID를 그룹에 일괄 등록. 요청 내부 중복과 이미 등록된 값은 건너뜀
필드 필수 제약
externalUserIds 예 비어 있지 않은 배열, 최대 1,000개
각 항목 예 공백 불가, trim 후 최대 128자, 그룹 이름과 같은 금지 문자 규칙
{
  "externalUserIds": ["pilot-001", "pilot-002", "pilot-001"]
}
{
  "status": 200,
  "message": "success",
  "data": {
    "requestedCount": 2,
    "addedCount": 1,
    "existingCount": 1
  }
}
  • 200: 등록 결과
  • 400: 배열, 개수, 항목 길이·문자 검증 실패
  • 401/403: 인증 또는 OWNER 검증 실패
  • 404: 그룹이 현재 워크스페이스에 없음

GET /v1/end-user-groups/{groupId}/members

  • path groupId: 그룹 UUID
  • query: page 기본 1, size 기본 20
  • 정렬: externalUserId 오름차순
  • 성공: 200 OK
  • 부수 효과: 없음
{
  "status": 200,
  "message": "success",
  "page": {
    "page": 1,
    "size": 20,
    "totalItems": 1,
    "totalPages": 1,
    "hasNext": false,
    "hasPrevious": false,
    "first": true,
    "last": true
  },
  "count": 1,
  "list": [
    {
      "id": "01900000-0000-7000-8000-000000000301",
      "externalUserId": "pilot-001",
      "matchedUser": true,
      "createdAt": "2026-09-23T10:11:00"
    }
  ]
}

matchedUser는 현재 workspace_end_user와 매칭되는지를 나타냅니다.

  • 200: 페이지 반환
  • 400/401/403: query, 인증 또는 OWNER 검증 실패
  • 404: 그룹이 현재 워크스페이스에 없음

DELETE /v1/end-user-groups/{groupId}/members/{memberId}

  • groupId: 그룹 UUID
  • memberId: 그룹 구성원 레코드 UUID이며 externalUserId가 아님
  • 성공: 200 OK
  • 부수 효과: 그룹에서 해당 구성원을 제거
{
  "status": 200,
  "message": "success"
}
  • 200: 제거 성공
  • 400: UUID 또는 헤더 형식 오류
  • 401/403: 인증 또는 OWNER 검증 실패
  • 404: 그룹 또는 구성원이 현재 워크스페이스·그룹에 없음