diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanRepository.kt
index 2220329..6c2f964 100644
--- a/src/main/kotlin/kr/co/vividnext/sodalive/can/CanRepository.kt
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/can/CanRepository.kt
@@ -8,10 +8,13 @@ import kr.co.vividnext.sodalive.can.charge.QCharge.charge
 import kr.co.vividnext.sodalive.can.payment.PaymentGateway
 import kr.co.vividnext.sodalive.can.payment.PaymentStatus
 import kr.co.vividnext.sodalive.can.payment.QPayment.payment
+import kr.co.vividnext.sodalive.can.use.CanUsage
 import kr.co.vividnext.sodalive.can.use.QUseCan.useCan
 import kr.co.vividnext.sodalive.can.use.UseCan
+import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
 import kr.co.vividnext.sodalive.member.Member
 import kr.co.vividnext.sodalive.member.QMember
+import kr.co.vividnext.sodalive.member.QMember.member
 import org.springframework.data.domain.Pageable
 import org.springframework.data.jpa.repository.JpaRepository
 import org.springframework.stereotype.Repository
@@ -23,6 +26,7 @@ interface CanQueryRepository {
     fun findAllByStatus(status: CanStatus): List<CanResponse>
     fun getCanUseStatus(member: Member, pageable: Pageable): List<UseCan>
     fun getCanChargeStatus(member: Member, pageable: Pageable, container: String): List<Charge>
+    fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan?
 }
 
 @Repository
@@ -93,4 +97,18 @@ class CanQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : CanQue
             .orderBy(charge.id.desc())
             .fetch()
     }
+
+    override fun isExistPaidLiveRoom(memberId: Long, roomId: Long): UseCan? {
+        return queryFactory
+            .selectFrom(useCan)
+            .innerJoin(useCan.member, member)
+            .innerJoin(useCan.room, liveRoom)
+            .where(
+                member.id.eq(memberId)
+                    .and(liveRoom.id.eq(roomId))
+                    .and(useCan.canUsage.eq(CanUsage.LIVE))
+            )
+            .orderBy(useCan.id.desc())
+            .fetchFirst()
+    }
 }
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensions.kt b/src/main/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensions.kt
new file mode 100644
index 0000000..ff1bb02
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/extensions/StringExtensions.kt
@@ -0,0 +1,9 @@
+package kr.co.vividnext.sodalive.extensions
+
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+fun String.convertLocalDateTime(format: String): LocalDateTime {
+    val dateTimeFormatter = DateTimeFormatter.ofPattern(format)
+    return LocalDateTime.parse(this, dateTimeFormatter)
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt
index 7ebacf8..7d1ebba 100644
--- a/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/recommend/LiveRecommendRepository.kt
@@ -4,7 +4,6 @@ import com.querydsl.core.types.Projections
 import com.querydsl.core.types.dsl.Expressions
 import com.querydsl.jpa.impl.JPAQueryFactory
 import kr.co.vividnext.sodalive.live.recommend.QRecommendLiveCreatorBanner.recommendLiveCreatorBanner
-import kr.co.vividnext.sodalive.live.room.LiveRoomType
 import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
 import kr.co.vividnext.sodalive.member.MemberRole
 import kr.co.vividnext.sodalive.member.QMember.member
@@ -63,7 +62,6 @@ class LiveRecommendRepository(private val queryFactory: JPAQueryFactory) {
             .where(
                 where
                     .and(liveRoom.isActive.isTrue)
-                    .and(liveRoom.type.ne(LiveRoomType.SECRET))
                     .and(liveRoom.channelName.isNotNull)
                     .and(liveRoom.channelName.isNotEmpty)
             )
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservation.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservation.kt
new file mode 100644
index 0000000..59cf6f2
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/reservation/LiveReservation.kt
@@ -0,0 +1,27 @@
+package kr.co.vividnext.sodalive.live.reservation
+
+import kr.co.vividnext.sodalive.common.BaseEntity
+import kr.co.vividnext.sodalive.live.room.LiveRoom
+import kr.co.vividnext.sodalive.member.Member
+import javax.persistence.Entity
+import javax.persistence.FetchType
+import javax.persistence.JoinColumn
+import javax.persistence.ManyToOne
+import javax.persistence.OneToOne
+
+@Entity
+data class LiveReservation(
+    var isActive: Boolean = true
+) : BaseEntity() {
+    @OneToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "member_id", nullable = false)
+    var member: Member? = null
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "room_id", nullable = true)
+    var room: LiveRoom? = null
+        set(value) {
+            value?.reservations!!.add(this)
+            field = value
+        }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateLiveRoomResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateLiveRoomResponse.kt
new file mode 100644
index 0000000..24fb1be
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateLiveRoomResponse.kt
@@ -0,0 +1,6 @@
+package kr.co.vividnext.sodalive.live.room
+
+data class CreateLiveRoomResponse(
+    val id: Long?,
+    val channelName: String?
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateSudaRoomRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateSudaRoomRequest.kt
new file mode 100644
index 0000000..2df98d8
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/CreateSudaRoomRequest.kt
@@ -0,0 +1,15 @@
+package kr.co.vividnext.sodalive.live.room
+
+data class CreateSudaRoomRequest(
+    val title: String,
+    val content: String,
+    val coverImageUrl: String? = null,
+    val isAdult: Boolean,
+    val tags: List<String>,
+    val numberOfPeople: Int,
+    val beginDateTimeString: String? = null,
+    val price: Int = 0,
+    val timezone: String,
+    val type: LiveRoomType = LiveRoomType.OPEN,
+    val password: String? = null
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt
index 5137e5c..85035a3 100644
--- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoom.kt
@@ -2,8 +2,10 @@ package kr.co.vividnext.sodalive.live.room
 
 import kr.co.vividnext.sodalive.can.use.UseCan
 import kr.co.vividnext.sodalive.common.BaseEntity
+import kr.co.vividnext.sodalive.live.reservation.LiveReservation
 import kr.co.vividnext.sodalive.member.Member
 import java.time.LocalDateTime
+import javax.persistence.CascadeType
 import javax.persistence.Column
 import javax.persistence.Entity
 import javax.persistence.EnumType
@@ -33,9 +35,15 @@ data class LiveRoom(
     @JoinColumn(name = "member_id", nullable = false)
     var member: Member? = null
 
+    @OneToMany(mappedBy = "room", cascade = [CascadeType.ALL])
+    var tags: MutableList<LiveRoomTag> = mutableListOf()
+
     @OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
     var useCan: MutableList<UseCan> = mutableListOf()
 
+    @OneToMany(mappedBy = "room", fetch = FetchType.LAZY)
+    var reservations: MutableList<LiveReservation> = mutableListOf()
+
     var channelName: String? = null
     var isActive: Boolean = true
 }
@@ -45,10 +53,7 @@ enum class LiveRoomType {
     OPEN,
 
     // 비공개
-    PRIVATE,
-
-    // 비밀방
-    SECRET
+    PRIVATE
 }
 
 enum class LiveRoomStatus {
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt
index ec927e4..c15d880 100644
--- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomController.kt
@@ -6,9 +6,13 @@ import kr.co.vividnext.sodalive.member.Member
 import org.springframework.data.domain.Pageable
 import org.springframework.security.core.annotation.AuthenticationPrincipal
 import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PathVariable
+import org.springframework.web.bind.annotation.PostMapping
 import org.springframework.web.bind.annotation.RequestMapping
 import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.bind.annotation.RequestPart
 import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.multipart.MultipartFile
 
 @RestController
 @RequestMapping("/live/room")
@@ -26,4 +30,28 @@ class LiveRoomController(private val service: LiveRoomService) {
 
         ApiResponse.ok(service.getRoomList(dateString, status, pageable, member, timezone))
     }
+
+    @PostMapping
+    fun createLiveRoom(
+        @RequestPart("coverImage") coverImage: MultipartFile?,
+        @RequestPart("request") requestString: String,
+        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
+    ) = run {
+        if (member == null) throw SodaException("로그인 정보를 확인해주세요.")
+
+        ApiResponse.ok(service.createLiveRoom(coverImage, requestString, member))
+    }
+
+    @GetMapping("/detail/{id}")
+    fun getRoomDetail(
+        @PathVariable id: Long,
+        @RequestParam timezone: String,
+        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
+    ) = run {
+        if (member != null) {
+            ApiResponse.ok(service.getRoomDetail(id, member, timezone))
+        } else {
+            throw SodaException("로그인 정보를 확인해주세요.")
+        }
+    }
 }
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt
index 82e227d..169ae95 100644
--- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomRepository.kt
@@ -5,6 +5,8 @@ import com.querydsl.core.types.Predicate
 import com.querydsl.core.types.dsl.CaseBuilder
 import com.querydsl.core.types.dsl.Expressions
 import com.querydsl.jpa.impl.JPAQueryFactory
+import kr.co.vividnext.sodalive.live.reservation.LiveReservation
+import kr.co.vividnext.sodalive.live.reservation.QLiveReservation.liveReservation
 import kr.co.vividnext.sodalive.live.room.QLiveRoom.liveRoom
 import kr.co.vividnext.sodalive.member.Member
 import kr.co.vividnext.sodalive.member.QMember
@@ -29,6 +31,9 @@ interface LiveRoomQueryRepository {
         timezone: String,
         isAdult: Boolean
     ): List<LiveRoom>
+
+    fun getLiveRoom(id: Long): LiveRoom?
+    fun getReservationList(roomId: Long): List<LiveReservation>
 }
 
 class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveRoomQueryRepository {
@@ -79,7 +84,6 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L
 
         where = where.and(liveRoom.isActive.isTrue)
             .and(liveRoom.member.isNotNull)
-            .and(liveRoom.type.ne(LiveRoomType.SECRET))
 
         return queryFactory
             .selectFrom(liveRoom)
@@ -98,6 +102,27 @@ class LiveRoomQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : L
             .fetch()
     }
 
+    override fun getLiveRoom(id: Long): LiveRoom? {
+        return queryFactory
+            .selectFrom(liveRoom)
+            .where(
+                liveRoom.id.eq(id)
+                    .and(liveRoom.isActive.isTrue)
+            )
+            .fetchFirst()
+    }
+
+    override fun getReservationList(roomId: Long): List<LiveReservation> {
+        return queryFactory
+            .selectFrom(liveReservation)
+            .innerJoin(liveReservation.room, liveRoom)
+            .where(
+                liveRoom.id.eq(roomId)
+                    .and(liveReservation.isActive.isTrue)
+            )
+            .fetch()
+    }
+
     private fun orderByFieldAccountId(
         memberId: Long,
         status: LiveRoomStatus,
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt
index 24a41b8..3115000 100644
--- a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomService.kt
@@ -1,11 +1,24 @@
 package kr.co.vividnext.sodalive.live.room
 
+import com.amazonaws.services.s3.model.ObjectMetadata
+import com.fasterxml.jackson.databind.ObjectMapper
+import kr.co.vividnext.sodalive.aws.s3.S3Uploader
+import kr.co.vividnext.sodalive.can.CanRepository
+import kr.co.vividnext.sodalive.common.SodaException
+import kr.co.vividnext.sodalive.extensions.convertLocalDateTime
+import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailManager
+import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailResponse
+import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
 import kr.co.vividnext.sodalive.live.room.info.LiveRoomInfoRedisRepository
+import kr.co.vividnext.sodalive.live.tag.LiveTagRepository
 import kr.co.vividnext.sodalive.member.Member
+import kr.co.vividnext.sodalive.utils.generateFileName
 import org.springframework.beans.factory.annotation.Value
 import org.springframework.data.domain.Pageable
 import org.springframework.data.repository.findByIdOrNull
 import org.springframework.stereotype.Service
+import org.springframework.web.multipart.MultipartFile
+import java.time.LocalDateTime
 import java.time.ZoneId
 import java.time.format.DateTimeFormatter
 
@@ -14,8 +27,15 @@ class LiveRoomService(
     private val repository: LiveRoomRepository,
     private val roomInfoRepository: LiveRoomInfoRedisRepository,
 
+    private val tagRepository: LiveTagRepository,
+    private val canRepository: CanRepository,
+    private val objectMapper: ObjectMapper,
+    private val s3Uploader: S3Uploader,
+
+    @Value("\${cloud.aws.s3.bucket}")
+    private val coverImageBucket: String,
     @Value("\${cloud.aws.cloud-front.host}")
-    private val coverImageHost: String
+    private val cloudFrontHost: String
 ) {
     fun getRoomList(
         dateString: String?,
@@ -61,7 +81,7 @@ class LiveRoomService(
                     coverImageUrl = if (it.coverImage!!.startsWith("https://")) {
                         it.coverImage!!
                     } else {
-                        "$coverImageHost/${it.coverImage!!}"
+                        "$cloudFrontHost/${it.coverImage!!}"
                     },
                     isReservation = false,
                     isPrivateRoom = it.type == LiveRoomType.PRIVATE
@@ -69,4 +89,182 @@ class LiveRoomService(
             }
             .toList()
     }
+
+    fun createLiveRoom(coverImage: MultipartFile?, requestString: String, member: Member): CreateLiveRoomResponse {
+        val request = objectMapper.readValue(requestString, CreateSudaRoomRequest::class.java)
+        if (request.coverImageUrl == null && coverImage == null) {
+            throw SodaException("커버이미지를 선택해 주세요.")
+        }
+
+        val now = LocalDateTime.now()
+        val beginDateTime = if (request.beginDateTimeString != null) {
+            request.beginDateTimeString.convertLocalDateTime("yyyy-MM-dd HH:mm")
+                .atZone(ZoneId.of(request.timezone))
+                .withZoneSameInstant(ZoneId.of("UTC"))
+                .toLocalDateTime()
+        } else {
+            now
+        }
+
+        if (
+            request.beginDateTimeString != null &&
+            beginDateTime < now.plusMinutes(30)
+        ) {
+            throw SodaException("현재시각 기준, 30분 이후부터 설정가능합니다.")
+        }
+
+        if (
+            request.type == LiveRoomType.PRIVATE &&
+            (request.password == null || request.password.length != 6)
+        ) {
+            throw SodaException("방 입장 비밀번호 6자리를 입력해 주세요.")
+        }
+
+        val room = LiveRoom(
+            title = request.title,
+            notice = request.content,
+            beginDateTime = beginDateTime,
+            numberOfPeople = request.numberOfPeople,
+            isAdult = request.isAdult,
+            price = request.price,
+            type = request.type,
+            password = request.password
+        )
+        room.member = member
+
+        if (request.beginDateTimeString == null) {
+            room.channelName = "SODA_LIVE_CHANNEL_" +
+                "${member.id!!}_${beginDateTime.year}_${beginDateTime.month}_${beginDateTime.dayOfMonth}_" +
+                "${beginDateTime.hour}_${beginDateTime.minute}"
+        }
+
+        request.tags.forEach {
+            val tag = tagRepository.findByTag(it)
+            if (tag != null) {
+                room.tags.add(LiveRoomTag(room, tag))
+
+                if (tag.tag.contains("음담패설")) {
+                    room.isAdult = true
+                }
+            }
+        }
+
+        val createdRoom = repository.save(room)
+        // 이미지 업로드
+        if (coverImage != null) {
+            val metadata = ObjectMetadata()
+            metadata.contentLength = coverImage.size
+
+            // 커버 이미지 파일명 생성
+            val coverImageFileName = generateFileName(prefix = "${room.id}-cover")
+
+            // 커버 이미지 업로드
+            val coverImagePath = s3Uploader.upload(
+                inputStream = coverImage.inputStream,
+                bucket = coverImageBucket,
+                filePath = "live_room_cover/${room.id}/$coverImageFileName",
+                metadata = metadata
+            )
+
+            room.coverImage = coverImagePath
+            room.bgImage = coverImagePath
+        } else {
+            room.coverImage = request.coverImageUrl
+            room.bgImage = request.coverImageUrl
+        }
+
+        return CreateLiveRoomResponse(createdRoom.id, createdRoom.channelName)
+    }
+
+    fun getRoomDetail(roomId: Long, member: Member, timezone: String): GetRoomDetailResponse {
+        val room = repository.getLiveRoom(id = roomId)
+            ?: throw SodaException("이미 종료된 방입니다")
+
+        if (room.isAdult && member.auth == null) {
+            throw SodaException("본인인증이 필요한 서비스 입니다.\n요즘라이브 마이페이지에서 본인인증 후 다시 이용해 주세요.")
+        }
+
+        val beginDateTime = room.beginDateTime
+            .atZone(ZoneId.of("UTC"))
+            .withZoneSameInstant(ZoneId.of(timezone))
+
+        val response = GetRoomDetailResponse(
+            roomId = roomId,
+            title = room.title,
+            content = room.notice,
+            price = room.price,
+            tags = room.tags.asSequence().filter { it.tag.isActive }.map { it.tag.tag }.toList(),
+            numberOfParticipantsTotal = room.numberOfPeople,
+            numberOfParticipants = 0,
+            channelName = room.channelName,
+            beginDateTime = beginDateTime.format(DateTimeFormatter.ofPattern("yyyy.MM.dd E hh:mm a")),
+            isPaid = false,
+            isPrivateRoom = room.type == LiveRoomType.PRIVATE,
+            password = room.password
+        )
+        response.manager = GetRoomDetailManager(room.member!!, cloudFrontHost = cloudFrontHost)
+
+        if (!room.channelName.isNullOrBlank()) {
+            val roomInfo = roomInfoRepository.findByIdOrNull(roomId)
+
+            if (roomInfo != null) {
+                response.isPaid = canRepository.isExistPaidLiveRoom(
+                    memberId = member.id!!,
+                    roomId = roomId
+                ) != null
+
+                val users = roomInfo.speakerList
+                    .asSequence()
+                    .map {
+                        GetRoomDetailUser(
+                            it.id,
+                            it.nickname,
+                            "$cloudFrontHost/${it.profileImage}"
+                        )
+                    }.toMutableList()
+
+                users.addAll(
+                    roomInfo.listenerList
+                        .asSequence()
+                        .map {
+                            GetRoomDetailUser(
+                                it.id,
+                                it.nickname,
+                                "$cloudFrontHost/${it.profileImage}"
+                            )
+                        }
+                )
+
+                users.addAll(
+                    roomInfo.managerList
+                        .asSequence()
+                        .map {
+                            GetRoomDetailUser(
+                                it.id,
+                                it.nickname,
+                                "$cloudFrontHost/${it.profileImage}"
+                            )
+                        }
+                )
+
+                response.participatingUsers = users
+                response.numberOfParticipants = users.size
+            }
+        } else {
+            val reservationList = repository.getReservationList(roomId)
+            response.participatingUsers = reservationList
+                .asSequence()
+                .map {
+                    if (it.member!!.id!! == member.id!!) {
+                        response.isPaid = true
+                    }
+
+                    GetRoomDetailUser(it.member!!, cloudFrontHost)
+                }
+                .toList()
+            response.numberOfParticipants = reservationList.size
+        }
+
+        return response
+    }
 }
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomTag.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomTag.kt
new file mode 100644
index 0000000..c822931
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/LiveRoomTag.kt
@@ -0,0 +1,19 @@
+package kr.co.vividnext.sodalive.live.room
+
+import kr.co.vividnext.sodalive.common.BaseEntity
+import kr.co.vividnext.sodalive.live.tag.LiveTag
+import javax.persistence.Entity
+import javax.persistence.FetchType
+import javax.persistence.JoinColumn
+import javax.persistence.ManyToOne
+
+@Entity
+class LiveRoomTag(
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "live_room_id", nullable = false)
+    var room: LiveRoom,
+
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "live_tag_id", nullable = false)
+    var tag: LiveTag
+) : BaseEntity()
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt
new file mode 100644
index 0000000..8a8a0d8
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/room/detail/GetRoomDetailResponse.kt
@@ -0,0 +1,66 @@
+package kr.co.vividnext.sodalive.live.room.detail
+
+import kr.co.vividnext.sodalive.member.Member
+import kr.co.vividnext.sodalive.member.MemberRole
+
+data class GetRoomDetailResponse(
+    val roomId: Long,
+    val price: Int,
+    val title: String,
+    val content: String,
+    var isPaid: Boolean,
+    val isPrivateRoom: Boolean,
+    val password: String?,
+    val tags: List<String>,
+    val channelName: String?,
+    val beginDateTime: String,
+    var numberOfParticipants: Int,
+    val numberOfParticipantsTotal: Int
+) {
+    var manager: GetRoomDetailManager? = null
+    var participatingUsers: List<GetRoomDetailUser> = listOf()
+}
+
+data class GetRoomDetailManager(
+    val id: Long,
+    val nickname: String,
+    val introduce: String,
+    val youtubeUrl: String?,
+    val instagramUrl: String?,
+    val websiteUrl: String?,
+    val blogUrl: String?,
+    val profileImageUrl: String,
+    val isCreator: Boolean
+) {
+    constructor(member: Member, cloudFrontHost: String) : this(
+        id = member.id!!,
+        nickname = member.nickname,
+        introduce = member.introduce,
+        youtubeUrl = member.youtubeUrl,
+        instagramUrl = member.instagramUrl,
+        websiteUrl = member.websiteUrl,
+        blogUrl = member.blogUrl,
+        profileImageUrl = if (member.profileImage != null) {
+            "$cloudFrontHost/${member.profileImage}"
+        } else {
+            "$cloudFrontHost/profile/default-profile.png"
+        },
+        isCreator = member.role == MemberRole.CREATOR
+    )
+}
+
+data class GetRoomDetailUser(
+    val id: Long,
+    val nickname: String,
+    val profileImageUrl: String
+) {
+    constructor(member: Member, cloudFrontHost: String) : this(
+        member.id!!,
+        member.nickname,
+        profileImageUrl = if (member.profileImage != null) {
+            "$cloudFrontHost/${member.profileImage}"
+        } else {
+            "$cloudFrontHost/profile/default-profile.png"
+        }
+    )
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/CreateLiveTagRequest.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/CreateLiveTagRequest.kt
new file mode 100644
index 0000000..80a1b59
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/CreateLiveTagRequest.kt
@@ -0,0 +1,3 @@
+package kr.co.vividnext.sodalive.live.tag
+
+data class CreateLiveTagRequest(val tag: String)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/GetLiveTagResponse.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/GetLiveTagResponse.kt
new file mode 100644
index 0000000..e478871
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/GetLiveTagResponse.kt
@@ -0,0 +1,9 @@
+package kr.co.vividnext.sodalive.live.tag
+
+import com.querydsl.core.annotations.QueryProjection
+
+data class GetLiveTagResponse @QueryProjection constructor(
+    val id: Long,
+    val tag: String,
+    val image: String
+)
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTag.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTag.kt
new file mode 100644
index 0000000..99b0735
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTag.kt
@@ -0,0 +1,17 @@
+package kr.co.vividnext.sodalive.live.tag
+
+import kr.co.vividnext.sodalive.common.BaseEntity
+import javax.persistence.Column
+import javax.persistence.Entity
+
+@Entity
+data class LiveTag(
+    @Column(unique = true, nullable = false)
+    var tag: String,
+    @Column(nullable = true)
+    var image: String? = null,
+    @Column(nullable = false)
+    var isActive: Boolean = true,
+    @Column(nullable = false)
+    var orders: Int = 1
+) : BaseEntity()
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagController.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagController.kt
new file mode 100644
index 0000000..d72d1d6
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagController.kt
@@ -0,0 +1,35 @@
+package kr.co.vividnext.sodalive.live.tag
+
+import kr.co.vividnext.sodalive.common.ApiResponse
+import kr.co.vividnext.sodalive.common.SodaException
+import kr.co.vividnext.sodalive.member.Member
+import org.springframework.security.access.prepost.PreAuthorize
+import org.springframework.security.core.annotation.AuthenticationPrincipal
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.PostMapping
+import org.springframework.web.bind.annotation.RequestMapping
+import org.springframework.web.bind.annotation.RequestPart
+import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.multipart.MultipartFile
+
+@RestController
+@RequestMapping("/live/tag")
+class LiveTagController(private val service: LiveTagService) {
+    @PostMapping
+    @PreAuthorize("hasRole('ADMIN')")
+    fun enrollmentLiveTag(
+        @RequestPart("image") image: MultipartFile,
+        @RequestPart("request") requestString: String
+    ) = ApiResponse.ok(service.enrollmentLiveTag(image, requestString), "등록되었습니다.")
+
+    @GetMapping
+    fun getTags(
+        @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?
+    ) = run {
+        if (member == null) {
+            throw SodaException("로그인 정보를 확인해주세요.")
+        }
+
+        ApiResponse.ok(service.getTags(member))
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagRepository.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagRepository.kt
new file mode 100644
index 0000000..009768f
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagRepository.kt
@@ -0,0 +1,40 @@
+package kr.co.vividnext.sodalive.live.tag
+
+import com.querydsl.jpa.impl.JPAQueryFactory
+import kr.co.vividnext.sodalive.live.tag.QLiveTag.liveTag
+import kr.co.vividnext.sodalive.member.MemberRole
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.stereotype.Repository
+
+@Repository
+interface LiveTagRepository : JpaRepository<LiveTag, Long>, LiveTagQueryRepository {
+    fun findByTag(it: String): LiveTag?
+}
+
+interface LiveTagQueryRepository {
+    fun getTags(role: MemberRole, isAdult: Boolean, cloudFrontHost: String): List<GetLiveTagResponse>
+}
+
+@Repository
+class LiveTagQueryRepositoryImpl(private val queryFactory: JPAQueryFactory) : LiveTagQueryRepository {
+    override fun getTags(role: MemberRole, isAdult: Boolean, cloudFrontHost: String): List<GetLiveTagResponse> {
+        var where = liveTag.isActive.isTrue
+
+        if (role != MemberRole.ADMIN && !isAdult) {
+            where = where.and(liveTag.tag.notIn("음담패설", "EDPS"))
+        }
+
+        return queryFactory
+            .select(
+                QGetLiveTagResponse(
+                    liveTag.id,
+                    liveTag.tag,
+                    liveTag.image.prepend("/").prepend(cloudFrontHost)
+                )
+            )
+            .from(liveTag)
+            .where(where)
+            .orderBy(liveTag.orders.asc())
+            .fetch()
+    }
+}
diff --git a/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagService.kt b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagService.kt
new file mode 100644
index 0000000..e168af4
--- /dev/null
+++ b/src/main/kotlin/kr/co/vividnext/sodalive/live/tag/LiveTagService.kt
@@ -0,0 +1,52 @@
+package kr.co.vividnext.sodalive.live.tag
+
+import com.amazonaws.services.s3.model.ObjectMetadata
+import com.fasterxml.jackson.databind.ObjectMapper
+import kr.co.vividnext.sodalive.aws.s3.S3Uploader
+import kr.co.vividnext.sodalive.common.SodaException
+import kr.co.vividnext.sodalive.member.Member
+import kr.co.vividnext.sodalive.utils.generateFileName
+import org.springframework.beans.factory.annotation.Value
+import org.springframework.stereotype.Service
+import org.springframework.web.multipart.MultipartFile
+
+@Service
+class LiveTagService(
+    private val repository: LiveTagRepository,
+
+    private val objectMapper: ObjectMapper,
+    private val s3Uploader: S3Uploader,
+
+    @Value("\${cloud.aws.s3.bucket}")
+    private val coverImageBucket: String,
+    @Value("\${cloud.aws.cloud-front.host}")
+    private val cloudFrontHost: String
+) {
+    fun enrollmentLiveTag(image: MultipartFile, requestString: String) {
+        val request = objectMapper.readValue(requestString, CreateLiveTagRequest::class.java)
+        tagExistCheck(request)
+
+        val tag = repository.save(LiveTag(request.tag))
+
+        val metadata = ObjectMetadata()
+        metadata.contentLength = image.size
+
+        val tagImageFileName = generateFileName(prefix = "${tag.id}-")
+        val tagImagePath = s3Uploader.upload(
+            inputStream = image.inputStream,
+            bucket = coverImageBucket,
+            filePath = "live_cover/${tag.id}/$tagImageFileName",
+            metadata = metadata
+        )
+
+        tag.image = tagImagePath
+    }
+
+    fun getTags(member: Member): List<GetLiveTagResponse> {
+        return repository.getTags(role = member.role, isAdult = member.auth != null, cloudFrontHost = cloudFrontHost)
+    }
+
+    fun tagExistCheck(request: CreateLiveTagRequest) {
+        repository.findByTag(request.tag)?.let { throw SodaException("이미 등록된 태그 입니다.") }
+    }
+}