diff --git a/livekit-rtc/livekit/rtc/room.py b/livekit-rtc/livekit/rtc/room.py
index 64a157cb..41c287a0 100644
--- a/livekit-rtc/livekit/rtc/room.py
+++ b/livekit-rtc/livekit/rtc/room.py
@@ -131,9 +131,10 @@ def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None:
         self._room_queue = BroadcastQueue[proto_ffi.FfiEvent]()
         self._info = proto_room.RoomInfo()
 
-        self.remote_participants: Dict[str, RemoteParticipant] = {}
-        self.connection_state = ConnectionState.CONN_DISCONNECTED
+        self._remote_participants: Dict[str, RemoteParticipant] = {}
+        self._connection_state = ConnectionState.CONN_DISCONNECTED
         self._first_sid_future = asyncio.Future[str]()
+        self._local_participant: LocalParticipant | None = None
 
     def __del__(self) -> None:
         if self._ffi_handle is not None:
@@ -151,6 +152,37 @@ async def sid(self) -> str:
 
         return await self._first_sid_future
 
+    @property
+    def local_participant(self) -> LocalParticipant:
+        """Gets the local participant in the room.
+
+        Returns:
+            LocalParticipant: The local participant in the room.
+        """
+        if self._local_participant is None:
+            raise Exception("cannot access local participant before connecting")
+
+        return self._local_participant
+
+    @property
+    def connection_state(self) -> ConnectionState.ValueType:
+        """Gets the connection state of the room.
+
+        Returns:
+            ConnectionState: The connection state of the room.
+        """
+        return self._connection_state
+
+    @property
+    def remote_participants(self) -> dict[str, RemoteParticipant]:
+        """Gets the remote participants in the room.
+
+        Returns:
+            dict[str, RemoteParticipant]: A dictionary of remote participants indexed by their
+            identity.
+        """
+        return self._remote_participants
+
     @property
     def name(self) -> str:
         """Gets the name of the room.
@@ -186,7 +218,7 @@ def isconnected(self) -> bool:
         """
         return (
             self._ffi_handle is not None
-            and self.connection_state != ConnectionState.CONN_DISCONNECTED
+            and self._connection_state != ConnectionState.CONN_DISCONNECTED
         )
 
     def on(self, event: EventTypes, callback: Optional[Callable] = None) -> Callable:
@@ -345,9 +377,9 @@ def on_participant_connected(participant):
         self._e2ee_manager = E2EEManager(self._ffi_handle.handle, options.e2ee)
 
         self._info = cb.connect.room.info
-        self.connection_state = ConnectionState.CONN_CONNECTED
+        self._connection_state = ConnectionState.CONN_CONNECTED
 
-        self.local_participant = LocalParticipant(
+        self._local_participant = LocalParticipant(
             self._room_queue, cb.connect.local_participant
         )
 
@@ -413,7 +445,7 @@ def _on_room_event(self, event: proto_room.RoomEvent):
             self.emit("participant_connected", rparticipant)
         elif which == "participant_disconnected":
             identity = event.participant_disconnected.participant_identity
-            rparticipant = self.remote_participants.pop(identity)
+            rparticipant = self._remote_participants.pop(identity)
             self.emit("participant_disconnected", rparticipant)
         elif which == "local_track_published":
             sid = event.local_track_published.track_sid
@@ -430,14 +462,14 @@ def _on_room_event(self, event: proto_room.RoomEvent):
             lpublication._first_subscription.set_result(None)
             self.emit("local_track_subscribed", lpublication.track)
         elif which == "track_published":
-            rparticipant = self.remote_participants[
+            rparticipant = self._remote_participants[
                 event.track_published.participant_identity
             ]
             rpublication = RemoteTrackPublication(event.track_published.publication)
             rparticipant.track_publications[rpublication.sid] = rpublication
             self.emit("track_published", rpublication, rparticipant)
         elif which == "track_unpublished":
-            rparticipant = self.remote_participants[
+            rparticipant = self._remote_participants[
                 event.track_unpublished.participant_identity
             ]
             rpublication = rparticipant.track_publications.pop(
@@ -447,7 +479,7 @@ def _on_room_event(self, event: proto_room.RoomEvent):
         elif which == "track_subscribed":
             owned_track_info = event.track_subscribed.track
             track_info = owned_track_info.info
-            rparticipant = self.remote_participants[
+            rparticipant = self._remote_participants[
                 event.track_subscribed.participant_identity
             ]
             rpublication = rparticipant.track_publications[track_info.sid]
@@ -466,7 +498,7 @@ def _on_room_event(self, event: proto_room.RoomEvent):
                 )
         elif which == "track_unsubscribed":
             identity = event.track_unsubscribed.participant_identity
-            rparticipant = self.remote_participants[identity]
+            rparticipant = self._remote_participants[identity]
             rpublication = rparticipant.track_publications[
                 event.track_unsubscribed.track_sid
             ]
@@ -476,7 +508,7 @@ def _on_room_event(self, event: proto_room.RoomEvent):
             self.emit("track_unsubscribed", track, rpublication, rparticipant)
         elif which == "track_subscription_failed":
             identity = event.track_subscription_failed.participant_identity
-            rparticipant = self.remote_participants[identity]
+            rparticipant = self._remote_participants[identity]
             error = event.track_subscription_failed.error
             self.emit(
                 "track_subscription_failed",
@@ -636,7 +668,7 @@ def _on_room_event(self, event: proto_room.RoomEvent):
             )
         elif which == "connection_state_changed":
             connection_state = event.connection_state_changed.state
-            self.connection_state = connection_state
+            self._connection_state = connection_state
             self.emit("connection_state_changed", connection_state)
         elif which == "connected":
             self.emit("connected")
@@ -651,7 +683,7 @@ def _retrieve_remote_participant(
         self, identity: str
     ) -> Optional[RemoteParticipant]:
         """Retrieve a remote participant by identity"""
-        return self.remote_participants.get(identity, None)
+        return self._remote_participants.get(identity, None)
 
     def _retrieve_participant(self, identity: str) -> Optional[Participant]:
         """Retrieve a local or remote participant by identity"""
@@ -663,11 +695,11 @@ def _retrieve_participant(self, identity: str) -> Optional[Participant]:
     def _create_remote_participant(
         self, owned_info: proto_participant.OwnedParticipant
     ) -> RemoteParticipant:
-        if owned_info.info.identity in self.remote_participants:
+        if owned_info.info.identity in self._remote_participants:
             raise Exception("participant already exists")
 
         participant = RemoteParticipant(owned_info)
-        self.remote_participants[participant.identity] = participant
+        self._remote_participants[participant.identity] = participant
         return participant
 
     def __repr__(self) -> str:
@@ -675,4 +707,4 @@ def __repr__(self) -> str:
         if self._first_sid_future.done():
             sid = self._first_sid_future.result()
 
-        return f"rtc.Room(sid={sid}, name={self.name}, metadata={self.metadata}, connection_state={self.connection_state})"
+        return f"rtc.Room(sid={sid}, name={self.name}, metadata={self.metadata}, connection_state={self._connection_state})"