Module livekit.api.access_token

Functions

def camel_to_snake(t: str) ‑> str
Expand source code
def camel_to_snake(t: str) -> str:
    return re.sub(r"(?<!^)(?=[A-Z])", "_", t).lower()
def snake_to_lower_camel(t: str) ‑> str
Expand source code
def snake_to_lower_camel(t: str) -> str:
    return "".join(word.capitalize() if i else word for i, word in enumerate(t.split("_")))

Classes

class AccessToken (api_key: str | None = None, api_secret: str | None = None)
Expand source code
class AccessToken:
    ParticipantKind = Literal["standard", "egress", "ingress", "sip", "agent"]

    def __init__(
        self,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
    ) -> None:
        api_key = api_key or os.getenv("LIVEKIT_API_KEY")
        api_secret = api_secret or os.getenv("LIVEKIT_API_SECRET")

        if not api_key or not api_secret:
            raise ValueError("api_key and api_secret must be set")

        self.api_key = api_key  # iss
        self.api_secret = api_secret
        self.claims = Claims()

        # default jwt claims
        self.identity = ""  # sub
        self.ttl = DEFAULT_TTL  # exp

    def with_ttl(self, ttl: datetime.timedelta) -> "AccessToken":
        self.ttl = ttl
        return self

    def with_grants(self, grants: VideoGrants) -> "AccessToken":
        self.claims.video = grants
        return self

    def with_sip_grants(self, grants: SIPGrants) -> "AccessToken":
        self.claims.sip = grants
        return self

    def with_observability_grants(self, grants: ObservabilityGrants) -> "AccessToken":
        self.claims.observability = grants
        return self

    def with_inference_grants(self, grants: InferenceGrants) -> "AccessToken":
        self.claims.inference = grants
        return self

    def with_agent_grants(self, grants: AgentGrants) -> "AccessToken":
        self.claims.agent = grants
        return self

    def with_agent_endpoint_grants(self, grants: AgentEndpointGrants) -> "AccessToken":
        self.claims.agent_endpoint = grants
        return self

    def with_agent_thread_grants(self, grants: AgentThreadGrants) -> "AccessToken":
        self.claims.agent_thread = grants
        return self

    def with_identity(self, identity: str) -> "AccessToken":
        self.identity = identity
        return self

    def with_kind(self, kind: ParticipantKind) -> "AccessToken":
        self.claims.kind = kind
        return self

    def with_name(self, name: str) -> "AccessToken":
        self.claims.name = name
        return self

    def with_metadata(self, metadata: str) -> "AccessToken":
        self.claims.metadata = metadata
        return self

    def with_attributes(self, attributes: dict[str, str]) -> "AccessToken":
        self.claims.attributes = attributes
        return self

    def with_sha256(self, sha256: str) -> "AccessToken":
        self.claims.sha256 = sha256
        return self

    def with_room_preset(self, preset: str) -> "AccessToken":
        self.claims.room_preset = preset
        return self

    def with_room_config(self, config: RoomConfiguration) -> "AccessToken":
        self.claims.room_config = config
        return self

    def to_jwt(self) -> str:
        video = self.claims.video
        if video and video.room_join and (not self.identity or not video.room):
            raise ValueError("identity and room must be set when joining a room")

        # we want to exclude None values from the token
        jwt_claims = self.claims.asdict()
        jwt_claims.update(
            {
                "sub": self.identity,
                "iss": self.api_key,
                "nbf": calendar.timegm(datetime.datetime.now(datetime.timezone.utc).utctimetuple()),
                "exp": calendar.timegm(
                    (datetime.datetime.now(datetime.timezone.utc) + self.ttl).utctimetuple()
                ),
            }
        )
        return jwt.encode(jwt_claims, self.api_secret, algorithm="HS256")

Class variables

var ParticipantKind

Methods

def to_jwt(self) ‑> str
Expand source code
def to_jwt(self) -> str:
    video = self.claims.video
    if video and video.room_join and (not self.identity or not video.room):
        raise ValueError("identity and room must be set when joining a room")

    # we want to exclude None values from the token
    jwt_claims = self.claims.asdict()
    jwt_claims.update(
        {
            "sub": self.identity,
            "iss": self.api_key,
            "nbf": calendar.timegm(datetime.datetime.now(datetime.timezone.utc).utctimetuple()),
            "exp": calendar.timegm(
                (datetime.datetime.now(datetime.timezone.utc) + self.ttl).utctimetuple()
            ),
        }
    )
    return jwt.encode(jwt_claims, self.api_secret, algorithm="HS256")
def with_agent_endpoint_grants(self,
grants: AgentEndpointGrants) ‑> AccessToken
Expand source code
def with_agent_endpoint_grants(self, grants: AgentEndpointGrants) -> "AccessToken":
    self.claims.agent_endpoint = grants
    return self
def with_agent_grants(self,
grants: AgentGrants) ‑> AccessToken
Expand source code
def with_agent_grants(self, grants: AgentGrants) -> "AccessToken":
    self.claims.agent = grants
    return self
def with_agent_thread_grants(self,
grants: AgentThreadGrants) ‑> AccessToken
Expand source code
def with_agent_thread_grants(self, grants: AgentThreadGrants) -> "AccessToken":
    self.claims.agent_thread = grants
    return self
def with_attributes(self, attributes: dict[str, str]) ‑> AccessToken
Expand source code
def with_attributes(self, attributes: dict[str, str]) -> "AccessToken":
    self.claims.attributes = attributes
    return self
def with_grants(self,
grants: VideoGrants) ‑> AccessToken
Expand source code
def with_grants(self, grants: VideoGrants) -> "AccessToken":
    self.claims.video = grants
    return self
def with_identity(self, identity: str) ‑> AccessToken
Expand source code
def with_identity(self, identity: str) -> "AccessToken":
    self.identity = identity
    return self
def with_inference_grants(self,
grants: InferenceGrants) ‑> AccessToken
Expand source code
def with_inference_grants(self, grants: InferenceGrants) -> "AccessToken":
    self.claims.inference = grants
    return self
def with_kind(self, kind: Literal['standard', 'egress', 'ingress', 'sip', 'agent']) ‑> AccessToken
Expand source code
def with_kind(self, kind: ParticipantKind) -> "AccessToken":
    self.claims.kind = kind
    return self
def with_metadata(self, metadata: str) ‑> AccessToken
Expand source code
def with_metadata(self, metadata: str) -> "AccessToken":
    self.claims.metadata = metadata
    return self
def with_name(self, name: str) ‑> AccessToken
Expand source code
def with_name(self, name: str) -> "AccessToken":
    self.claims.name = name
    return self
def with_observability_grants(self,
grants: ObservabilityGrants) ‑> AccessToken
Expand source code
def with_observability_grants(self, grants: ObservabilityGrants) -> "AccessToken":
    self.claims.observability = grants
    return self
def with_room_config(self, config: room.RoomConfiguration) ‑> AccessToken
Expand source code
def with_room_config(self, config: RoomConfiguration) -> "AccessToken":
    self.claims.room_config = config
    return self
def with_room_preset(self, preset: str) ‑> AccessToken
Expand source code
def with_room_preset(self, preset: str) -> "AccessToken":
    self.claims.room_preset = preset
    return self
def with_sha256(self, sha256: str) ‑> AccessToken
Expand source code
def with_sha256(self, sha256: str) -> "AccessToken":
    self.claims.sha256 = sha256
    return self
def with_sip_grants(self,
grants: SIPGrants) ‑> AccessToken
Expand source code
def with_sip_grants(self, grants: SIPGrants) -> "AccessToken":
    self.claims.sip = grants
    return self
def with_ttl(self, ttl: datetime.timedelta) ‑> AccessToken
Expand source code
def with_ttl(self, ttl: datetime.timedelta) -> "AccessToken":
    self.ttl = ttl
    return self
class AgentEndpointGrants (call: bool = False, agent_name: str = '', deployment: str = '')
Expand source code
@dataclass
class AgentEndpointGrants:
    # invoke an agent's non-public HTTP endpoints
    call: bool = False
    # restrict to one agent; empty grants every agent in the project
    agent_name: str = ""
    # restrict to one deployment; empty grants every deployment
    deployment: str = ""

AgentEndpointGrants(call: bool = False, agent_name: str = '', deployment: str = '')

Instance variables

var agent_name : str
var call : bool
var deployment : str
class AgentGrants (admin: bool = False,
simulation_admin: bool = False,
database_admin: bool = False,
dispatch_admin: bool = False)
Expand source code
@dataclass
class AgentGrants:
    # create/update/delete Cloud Agents
    admin: bool = False
    # manage simulations and scenarios for evaluating agents
    simulation_admin: bool = False
    # access the project's agent databases
    database_admin: bool = False
    # manage the project's agent dispatch queue
    dispatch_admin: bool = False

AgentGrants(admin: bool = False, simulation_admin: bool = False, database_admin: bool = False, dispatch_admin: bool = False)

Instance variables

var admin : bool
var database_admin : bool
var dispatch_admin : bool
var simulation_admin : bool
class AgentThreadGrants (all: bool = False,
scope: str = '',
thread_ids: List[str] | None = None,
list: bool = False,
create: bool = False,
write: bool = False,
read: bool = False,
delete: bool = False)
Expand source code
@dataclass
class AgentThreadGrants:
    # every thread and scope of the project; the action flags still apply
    all: bool = False
    # scope in which threads may be listed and created
    scope: str = ""
    # threads that may be read, written and deleted
    thread_ids: Optional[List[str]] = None
    list: bool = False
    create: bool = False
    write: bool = False
    read: bool = False
    delete: bool = False

AgentThreadGrants(all: bool = False, scope: str = '', thread_ids: Optional[List[str]] = None, list: bool = False, create: bool = False, write: bool = False, read: bool = False, delete: bool = False)

Instance variables

var all : bool
var create : bool
var delete : bool
var list : bool
var read : bool
var scope : str
var thread_ids : List[str] | None
var write : bool
class Claims (identity: str = '',
name: str = '',
kind: str = '',
metadata: str = '',
video: VideoGrants | None = None,
sip: SIPGrants | None = None,
inference: InferenceGrants | None = None,
observability: ObservabilityGrants | None = None,
agent: AgentGrants | None = None,
agent_endpoint: AgentEndpointGrants | None = None,
agent_thread: AgentThreadGrants | None = None,
attributes: dict[str, str] | None = None,
sha256: str | None = None,
room_preset: str | None = None,
room_config: room.RoomConfiguration | None = None)
Expand source code
@dataclass
class Claims:
    identity: str = ""
    name: str = ""
    kind: str = ""
    metadata: str = ""
    video: Optional[VideoGrants] = None
    sip: Optional[SIPGrants] = None
    inference: Optional[InferenceGrants] = None
    observability: Optional[ObservabilityGrants] = None
    agent: Optional[AgentGrants] = None
    agent_endpoint: Optional[AgentEndpointGrants] = None
    agent_thread: Optional[AgentThreadGrants] = None
    attributes: Optional[dict[str, str]] = None
    sha256: Optional[str] = None
    room_preset: Optional[str] = None
    room_config: Optional[RoomConfiguration] = None

    def asdict(self) -> dict:
        # in order to produce minimal jwt size, exclude None or empty values
        claims = dataclasses.asdict(
            self,
            dict_factory=lambda items: {
                snake_to_lower_camel(k): v for k, v in items if v is not None and v != ""
            },
        )
        if self.room_config:
            claims["roomConfig"] = MessageToDict(self.room_config)
        return claims

Claims(identity: str = '', name: str = '', kind: str = '', metadata: str = '', video: Optional[livekit.api.access_token.VideoGrants] = None, sip: Optional[livekit.api.access_token.SIPGrants] = None, inference: Optional[livekit.api.access_token.InferenceGrants] = None, observability: Optional[livekit.api.access_token.ObservabilityGrants] = None, agent: Optional[livekit.api.access_token.AgentGrants] = None, agent_endpoint: Optional[livekit.api.access_token.AgentEndpointGrants] = None, agent_thread: Optional[livekit.api.access_token.AgentThreadGrants] = None, attributes: Optional[dict[str, str]] = None, sha256: Optional[str] = None, room_preset: Optional[str] = None, room_config: Optional[room.RoomConfiguration] = None)

Instance variables

var agent : AgentGrants | None
var agent_endpoint : AgentEndpointGrants | None
var agent_thread : AgentThreadGrants | None
var attributes : dict[str, str] | None
var identity : str
var inference : InferenceGrants | None
var kind : str
var metadata : str
var name : str
var observability : ObservabilityGrants | None
var room_config : room.RoomConfiguration | None
var room_preset : str | None
var sha256 : str | None
var sip : SIPGrants | None
var video : VideoGrants | None

Methods

def asdict(self) ‑> dict
Expand source code
def asdict(self) -> dict:
    # in order to produce minimal jwt size, exclude None or empty values
    claims = dataclasses.asdict(
        self,
        dict_factory=lambda items: {
            snake_to_lower_camel(k): v for k, v in items if v is not None and v != ""
        },
    )
    if self.room_config:
        claims["roomConfig"] = MessageToDict(self.room_config)
    return claims
class InferenceGrants (perform: bool = False)
Expand source code
@dataclass
class InferenceGrants:
    # perform inference
    perform: bool = False

InferenceGrants(perform: bool = False)

Instance variables

var perform : bool
class ObservabilityGrants (write: bool = False)
Expand source code
@dataclass
class ObservabilityGrants:
    # write grants to publish observability data
    write: bool = False

ObservabilityGrants(write: bool = False)

Instance variables

var write : bool
class RoomConfiguration (*args, **kwargs)

A ProtocolMessage

Ancestors

  • google._upb._message.Message
  • google.protobuf.message.Message

Class variables

var DESCRIPTOR
var TagsEntry

A ProtocolMessage

class SIPGrants (admin: bool = False, call: bool = False)
Expand source code
@dataclass
class SIPGrants:
    # manage sip resources
    admin: bool = False
    # make outbound calls
    call: bool = False

SIPGrants(admin: bool = False, call: bool = False)

Instance variables

var admin : bool
var call : bool
class TokenVerifier (api_key: str | None = None,
api_secret: str | None = None,
*,
leeway: datetime.timedelta = datetime.timedelta(seconds=60))
Expand source code
class TokenVerifier:
    def __init__(
        self,
        api_key: Optional[str] = None,
        api_secret: Optional[str] = None,
        *,
        leeway: datetime.timedelta = DEFAULT_LEEWAY,
    ) -> None:
        api_key = api_key or os.getenv("LIVEKIT_API_KEY")
        api_secret = api_secret or os.getenv("LIVEKIT_API_SECRET")

        self.api_key = api_key
        self.api_secret = api_secret
        self._leeway = leeway

    def verify(self, token: str, *, verify_signature: bool = True) -> Claims:
        if verify_signature and (not self.api_key or not self.api_secret):
            raise ValueError("api_key and api_secret must be set")

        # First-party minters always set exp. Without this, a hand-rolled token
        # with a valid signature and no exp verifies forever (livekit/protocol#1706).
        if verify_signature:
            claims = jwt.decode(
                token,
                key=self.api_secret or "",
                issuer=self.api_key or "",
                algorithms=["HS256"],
                leeway=self._leeway.total_seconds(),
                options={"verify_signature": True, "require": ["exp"]},
            )
        else:
            claims = jwt.decode(
                token,
                key=self.api_secret or "",
                issuer=self.api_key or "",
                algorithms=["HS256"],
                leeway=self._leeway.total_seconds(),
                options={"verify_signature": False},
            )
        video_dict = claims.get("video", dict())
        video_dict = {camel_to_snake(k): v for k, v in video_dict.items()}
        video_dict = {k: v for k, v in video_dict.items() if k in VideoGrants.__dataclass_fields__}
        video = VideoGrants(**video_dict)

        sip_dict = claims.get("sip", dict())
        sip_dict = {camel_to_snake(k): v for k, v in sip_dict.items()}
        sip_dict = {k: v for k, v in sip_dict.items() if k in SIPGrants.__dataclass_fields__}
        sip = SIPGrants(**sip_dict)

        inference_dict = claims.get("inference", dict())
        inference_dict = {camel_to_snake(k): v for k, v in inference_dict.items()}
        inference_dict = {
            k: v for k, v in inference_dict.items() if k in InferenceGrants.__dataclass_fields__
        }
        inference = InferenceGrants(**inference_dict)

        observability_dict = claims.get("observability", dict())
        observability_dict = {camel_to_snake(k): v for k, v in observability_dict.items()}
        observability_dict = {
            k: v
            for k, v in observability_dict.items()
            if k in ObservabilityGrants.__dataclass_fields__
        }
        observability = ObservabilityGrants(**observability_dict)

        grant_claims = Claims(
            identity=claims.get("sub", ""),
            name=claims.get("name", ""),
            kind=claims.get("kind", ""),
            video=video,
            sip=sip,
            inference=inference,
            observability=observability,
            attributes=claims.get("attributes", {}),
            metadata=claims.get("metadata", ""),
            sha256=claims.get("sha256", ""),
            agent=_parse_grants(claims, "agent", AgentGrants),
            agent_endpoint=_parse_grants(claims, "agentEndpoint", AgentEndpointGrants),
            agent_thread=_parse_grants(claims, "agentThread", AgentThreadGrants),
        )

        if claims.get("roomPreset"):
            grant_claims.room_preset = claims.get("roomPreset")
        room_config = claims.get("roomConfig")
        if room_config:
            grant_claims.room_config = ParseDict(
                room_config,
                RoomConfiguration(),
                ignore_unknown_fields=True,
            )

        return grant_claims

Methods

def verify(self, token: str, *, verify_signature: bool = True) ‑> Claims
Expand source code
def verify(self, token: str, *, verify_signature: bool = True) -> Claims:
    if verify_signature and (not self.api_key or not self.api_secret):
        raise ValueError("api_key and api_secret must be set")

    # First-party minters always set exp. Without this, a hand-rolled token
    # with a valid signature and no exp verifies forever (livekit/protocol#1706).
    if verify_signature:
        claims = jwt.decode(
            token,
            key=self.api_secret or "",
            issuer=self.api_key or "",
            algorithms=["HS256"],
            leeway=self._leeway.total_seconds(),
            options={"verify_signature": True, "require": ["exp"]},
        )
    else:
        claims = jwt.decode(
            token,
            key=self.api_secret or "",
            issuer=self.api_key or "",
            algorithms=["HS256"],
            leeway=self._leeway.total_seconds(),
            options={"verify_signature": False},
        )
    video_dict = claims.get("video", dict())
    video_dict = {camel_to_snake(k): v for k, v in video_dict.items()}
    video_dict = {k: v for k, v in video_dict.items() if k in VideoGrants.__dataclass_fields__}
    video = VideoGrants(**video_dict)

    sip_dict = claims.get("sip", dict())
    sip_dict = {camel_to_snake(k): v for k, v in sip_dict.items()}
    sip_dict = {k: v for k, v in sip_dict.items() if k in SIPGrants.__dataclass_fields__}
    sip = SIPGrants(**sip_dict)

    inference_dict = claims.get("inference", dict())
    inference_dict = {camel_to_snake(k): v for k, v in inference_dict.items()}
    inference_dict = {
        k: v for k, v in inference_dict.items() if k in InferenceGrants.__dataclass_fields__
    }
    inference = InferenceGrants(**inference_dict)

    observability_dict = claims.get("observability", dict())
    observability_dict = {camel_to_snake(k): v for k, v in observability_dict.items()}
    observability_dict = {
        k: v
        for k, v in observability_dict.items()
        if k in ObservabilityGrants.__dataclass_fields__
    }
    observability = ObservabilityGrants(**observability_dict)

    grant_claims = Claims(
        identity=claims.get("sub", ""),
        name=claims.get("name", ""),
        kind=claims.get("kind", ""),
        video=video,
        sip=sip,
        inference=inference,
        observability=observability,
        attributes=claims.get("attributes", {}),
        metadata=claims.get("metadata", ""),
        sha256=claims.get("sha256", ""),
        agent=_parse_grants(claims, "agent", AgentGrants),
        agent_endpoint=_parse_grants(claims, "agentEndpoint", AgentEndpointGrants),
        agent_thread=_parse_grants(claims, "agentThread", AgentThreadGrants),
    )

    if claims.get("roomPreset"):
        grant_claims.room_preset = claims.get("roomPreset")
    room_config = claims.get("roomConfig")
    if room_config:
        grant_claims.room_config = ParseDict(
            room_config,
            RoomConfiguration(),
            ignore_unknown_fields=True,
        )

    return grant_claims
class VideoGrants (room_create: bool | None = None,
room_list: bool | None = None,
room_record: bool | None = None,
room_admin: bool | None = None,
room_join: bool | None = None,
room: str = '',
destination_room: str | None = None,
can_publish: bool = True,
can_subscribe: bool = True,
can_publish_data: bool = True,
can_publish_sources: List[str] | None = None,
can_update_own_metadata: bool | None = None,
ingress_admin: bool | None = None,
hidden: bool | None = None,
recorder: bool | None = None,
agent: bool | None = None,
can_manage_agent_session: bool | None = None,
can_subscribe_metrics: bool | None = None)
Expand source code
@dataclass
class VideoGrants:
    # actions on rooms
    room_create: Optional[bool] = None
    room_list: Optional[bool] = None
    room_record: Optional[bool] = None

    # actions on a particular room
    room_admin: Optional[bool] = None
    room_join: Optional[bool] = None
    room: str = ""

    # allows forwarding participant to room
    destination_room: Optional[str] = None

    # permissions within a room
    can_publish: bool = True
    can_subscribe: bool = True
    can_publish_data: bool = True

    # TrackSource types that a participant may publish.
    # When set, it supersedes CanPublish. Only sources explicitly set here can be
    # published
    can_publish_sources: Optional[List[str]] = None

    # by default, a participant is not allowed to update its own metadata
    can_update_own_metadata: Optional[bool] = None

    # actions on ingresses
    ingress_admin: Optional[bool] = None  # applies to all ingress

    # participant is not visible to other participants (useful when making bots)
    hidden: Optional[bool] = None

    # [deprecated] indicates to the room that current participant is a recorder
    recorder: Optional[bool] = None

    # indicates that the holder can register as an Agent framework worker
    agent: Optional[bool] = None

    # allow participant to manage an agent session via RemoteSession
    can_manage_agent_session: Optional[bool] = None

    # allow participant to subscribe to metrics
    can_subscribe_metrics: Optional[bool] = None

VideoGrants(room_create: Optional[bool] = None, room_list: Optional[bool] = None, room_record: Optional[bool] = None, room_admin: Optional[bool] = None, room_join: Optional[bool] = None, room: str = '', destination_room: Optional[str] = None, can_publish: bool = True, can_subscribe: bool = True, can_publish_data: bool = True, can_publish_sources: Optional[List[str]] = None, can_update_own_metadata: Optional[bool] = None, ingress_admin: Optional[bool] = None, hidden: Optional[bool] = None, recorder: Optional[bool] = None, agent: Optional[bool] = None, can_manage_agent_session: Optional[bool] = None, can_subscribe_metrics: Optional[bool] = None)

Instance variables

var agent : bool | None
var can_manage_agent_session : bool | None
var can_publish : bool
var can_publish_data : bool
var can_publish_sources : List[str] | None
var can_subscribe : bool
var can_subscribe_metrics : bool | None
var can_update_own_metadata : bool | None
var destination_room : str | None
var hidden : bool | None
var ingress_admin : bool | None
var recorder : bool | None
var room : str
var room_admin : bool | None
var room_create : bool | None
var room_join : bool | None
var room_list : bool | None
var room_record : bool | None