From 6de0cdf9fbf74e12b724380c9ba4a06d4bc1db20 Mon Sep 17 00:00:00 2001 From: roam-sdk-bot Date: Thu, 3 Sep 2026 19:28:03 +0000 Subject: [PATCH] Regenerate SDK from OpenAPI spec Source: WonderInventions/developer-ro-am@3d062c1acfff3beeeb05057ad28acc4c5190a5e8 Branch: master --- src/roamhq/.fern/metadata.json | 2 +- src/roamhq/__init__.py | 9 + src/roamhq/client.py | 19 + src/roamhq/core/client_wrapper.py | 1 + src/roamhq/group/client.py | 10 + src/roamhq/group/raw_client.py | 10 + src/roamhq/guest_badges/__init__.py | 36 + src/roamhq/guest_badges/client.py | 334 +++++++++ src/roamhq/guest_badges/raw_client.py | 677 ++++++++++++++++++ src/roamhq/guest_badges/types/__init__.py | 36 + .../types/guest_badge_revoke_response.py | 24 + src/roamhq/reference.md | 279 +++++++- src/roamhq/types/__init__.py | 3 + src/roamhq/types/guest_badge.py | 68 ++ .../types/webhook_subscription_filter.py | 26 +- src/roamhq/users/client.py | 50 +- src/roamhq/users/raw_client.py | 42 +- src/roamhq/webhook/client.py | 50 +- src/roamhq/webhook/raw_client.py | 44 +- 19 files changed, 1660 insertions(+), 60 deletions(-) create mode 100644 src/roamhq/guest_badges/__init__.py create mode 100644 src/roamhq/guest_badges/client.py create mode 100644 src/roamhq/guest_badges/raw_client.py create mode 100644 src/roamhq/guest_badges/types/__init__.py create mode 100644 src/roamhq/guest_badges/types/guest_badge_revoke_response.py create mode 100644 src/roamhq/types/guest_badge.py diff --git a/src/roamhq/.fern/metadata.json b/src/roamhq/.fern/metadata.json index e6bd928..c99b8cb 100644 --- a/src/roamhq/.fern/metadata.json +++ b/src/roamhq/.fern/metadata.json @@ -6,7 +6,7 @@ "package_name": "roamhq", "client_class_name": "RoamClient" }, - "originGitCommit": "e226955133092595744390682ba317d4ee33f224", + "originGitCommit": "3d062c1acfff3beeeb05057ad28acc4c5190a5e8", "originGitCommitIsDirty": false, "invokedBy": "ci", "ciProvider": "github" diff --git a/src/roamhq/__init__.py b/src/roamhq/__init__.py index 0d7f0a2..d009f38 100644 --- a/src/roamhq/__init__.py +++ b/src/roamhq/__init__.py @@ -29,6 +29,7 @@ GroupMember, GroupMemberRole, GroupType, + GuestBadge, LobbyBooking, LobbyBookingHost, LobbyBookingInvitee, @@ -80,6 +81,7 @@ conversation, group, groups, + guest_badges, item, lobby, magicast, @@ -156,6 +158,7 @@ MembersGroupResponse, ) from .groups import GroupsListResponseItem + from .guest_badges import GuestBadgeRevokeResponse from .lobby import ListBookingsLobbyResponse, ListLobbyResponse, ListLobbyResponseLobbiesItem from .magicast import ListMagicastResponse from .magicasts import MagicastShareLinkResponse @@ -237,6 +240,8 @@ "GroupMemberRole": ".types", "GroupType": ".types", "GroupsListResponseItem": ".groups", + "GuestBadge": ".types", + "GuestBadgeRevokeResponse": ".guest_badges", "HistoryChatResponse": ".chat", "InfoMeetingResponse": ".meeting", "InfoMeetingResponseChaptersItem": ".meeting", @@ -356,6 +361,7 @@ "conversation": ".conversation", "group": ".group", "groups": ".groups", + "guest_badges": ".guest_badges", "item": ".item", "lobby": ".lobby", "magicast": ".magicast", @@ -438,6 +444,8 @@ def __dir__(): "GroupMemberRole", "GroupType", "GroupsListResponseItem", + "GuestBadge", + "GuestBadgeRevokeResponse", "HistoryChatResponse", "InfoMeetingResponse", "InfoMeetingResponseChaptersItem", @@ -557,6 +565,7 @@ def __dir__(): "conversation", "group", "groups", + "guest_badges", "item", "lobby", "magicast", diff --git a/src/roamhq/client.py b/src/roamhq/client.py index 691744b..0cbc79a 100644 --- a/src/roamhq/client.py +++ b/src/roamhq/client.py @@ -16,6 +16,7 @@ from .conversation.client import AsyncConversationClient, ConversationClient from .group.client import AsyncGroupClient, GroupClient from .groups.client import AsyncGroupsClient, GroupsClient + from .guest_badges.client import AsyncGuestBadgesClient, GuestBadgesClient from .item.client import AsyncItemClient, ItemClient from .lobby.client import AsyncLobbyClient, LobbyClient from .magicast.client import AsyncMagicastClient, MagicastClient @@ -136,6 +137,7 @@ def __init__( self._magicasts: typing.Optional[MagicastsClient] = None self._group: typing.Optional[GroupClient] = None self._groups: typing.Optional[GroupsClient] = None + self._guest_badges: typing.Optional[GuestBadgesClient] = None self._token: typing.Optional[TokenClient] = None self._webhook: typing.Optional[WebhookClient] = None @@ -275,6 +277,14 @@ def groups(self): self._groups = GroupsClient(client_wrapper=self._client_wrapper) return self._groups + @property + def guest_badges(self): + if self._guest_badges is None: + from .guest_badges.client import GuestBadgesClient # noqa: E402 + + self._guest_badges = GuestBadgesClient(client_wrapper=self._client_wrapper) + return self._guest_badges + @property def token(self): if self._token is None: @@ -418,6 +428,7 @@ def __init__( self._magicasts: typing.Optional[AsyncMagicastsClient] = None self._group: typing.Optional[AsyncGroupClient] = None self._groups: typing.Optional[AsyncGroupsClient] = None + self._guest_badges: typing.Optional[AsyncGuestBadgesClient] = None self._token: typing.Optional[AsyncTokenClient] = None self._webhook: typing.Optional[AsyncWebhookClient] = None @@ -557,6 +568,14 @@ def groups(self): self._groups = AsyncGroupsClient(client_wrapper=self._client_wrapper) return self._groups + @property + def guest_badges(self): + if self._guest_badges is None: + from .guest_badges.client import AsyncGuestBadgesClient # noqa: E402 + + self._guest_badges = AsyncGuestBadgesClient(client_wrapper=self._client_wrapper) + return self._guest_badges + @property def token(self): if self._token is None: diff --git a/src/roamhq/core/client_wrapper.py b/src/roamhq/core/client_wrapper.py index ed30d24..18846c9 100644 --- a/src/roamhq/core/client_wrapper.py +++ b/src/roamhq/core/client_wrapper.py @@ -37,6 +37,7 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { + "User-Agent": "roamhq/0.1.1", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", diff --git a/src/roamhq/group/client.py b/src/roamhq/group/client.py index 7b95267..2b75723 100644 --- a/src/roamhq/group/client.py +++ b/src/roamhq/group/client.py @@ -151,6 +151,9 @@ def create( that capability. Groups require at least one member. Users can be specified by user ID or email address. + Unrecognized emails are invited as group members only — they do not receive a + [Guest Badge](https://developer.ro.am/docs/guides/guest-badges) unless you also call + [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create). **Required scope:** `group:write` @@ -360,6 +363,8 @@ def add( Members can be specified by user ID or email address. Each member must be assigned a role (member or admin). + Adding an unrecognized email does **not** grant a [Guest Badge](https://developer.ro.am/docs/guides/guest-badges). Use [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create) first if the person is not a workspace member. + Apps may add members to a group if one of the following conditions is true: 1. It is a public group in their Roam. 2. They are a member of the group. @@ -656,6 +661,9 @@ async def create( that capability. Groups require at least one member. Users can be specified by user ID or email address. + Unrecognized emails are invited as group members only — they do not receive a + [Guest Badge](https://developer.ro.am/docs/guides/guest-badges) unless you also call + [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create). **Required scope:** `group:write` @@ -897,6 +905,8 @@ async def add( Members can be specified by user ID or email address. Each member must be assigned a role (member or admin). + Adding an unrecognized email does **not** grant a [Guest Badge](https://developer.ro.am/docs/guides/guest-badges). Use [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create) first if the person is not a workspace member. + Apps may add members to a group if one of the following conditions is true: 1. It is a public group in their Roam. 2. They are a member of the group. diff --git a/src/roamhq/group/raw_client.py b/src/roamhq/group/raw_client.py index 4f425de..058092f 100644 --- a/src/roamhq/group/raw_client.py +++ b/src/roamhq/group/raw_client.py @@ -316,6 +316,9 @@ def create( that capability. Groups require at least one member. Users can be specified by user ID or email address. + Unrecognized emails are invited as group members only — they do not receive a + [Guest Badge](https://developer.ro.am/docs/guides/guest-badges) unless you also call + [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create). **Required scope:** `group:write` @@ -798,6 +801,8 @@ def add( Members can be specified by user ID or email address. Each member must be assigned a role (member or admin). + Adding an unrecognized email does **not** grant a [Guest Badge](https://developer.ro.am/docs/guides/guest-badges). Use [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create) first if the person is not a workspace member. + Apps may add members to a group if one of the following conditions is true: 1. It is a public group in their Roam. 2. They are a member of the group. @@ -1427,6 +1432,9 @@ async def create( that capability. Groups require at least one member. Users can be specified by user ID or email address. + Unrecognized emails are invited as group members only — they do not receive a + [Guest Badge](https://developer.ro.am/docs/guides/guest-badges) unless you also call + [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create). **Required scope:** `group:write` @@ -1911,6 +1919,8 @@ async def add( Members can be specified by user ID or email address. Each member must be assigned a role (member or admin). + Adding an unrecognized email does **not** grant a [Guest Badge](https://developer.ro.am/docs/guides/guest-badges). Use [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create) first if the person is not a workspace member. + Apps may add members to a group if one of the following conditions is true: 1. It is a public group in their Roam. 2. They are a member of the group. diff --git a/src/roamhq/guest_badges/__init__.py b/src/roamhq/guest_badges/__init__.py new file mode 100644 index 0000000..05e4897 --- /dev/null +++ b/src/roamhq/guest_badges/__init__.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +from __future__ import annotations + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import GuestBadgeRevokeResponse +_dynamic_imports: typing.Dict[str, str] = {"GuestBadgeRevokeResponse": ".types"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["GuestBadgeRevokeResponse"] diff --git a/src/roamhq/guest_badges/client.py b/src/roamhq/guest_badges/client.py new file mode 100644 index 0000000..3c16f84 --- /dev/null +++ b/src/roamhq/guest_badges/client.py @@ -0,0 +1,334 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.request_options import RequestOptions +from ..types.guest_badge import GuestBadge +from .raw_client import AsyncRawGuestBadgesClient, RawGuestBadgesClient +from .types.guest_badge_revoke_response import GuestBadgeRevokeResponse + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class GuestBadgesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawGuestBadgesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawGuestBadgesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawGuestBadgesClient + """ + return self._raw_client + + def guest_badge_create( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + visit_permission: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> GuestBadge: + """ + Grant a Guest Badge so an email that is **not** a workspace member can + visit a host in Roam. + + This is **not** an [On-Air event guest](https://developer.ro.am/docs/onair-api/onair-api). It is + also **not** implied by [`group.add`](https://developer.ro.am/docs/api/group-add): adding an email + to a group does not mint a badge or send the invite. Typical onboarding is + `guest.badge.create` then `group.add`. + + Repeating create for the same host and email returns the existing badge + (`visitPermission` is **not** updated) and does not re-send the invite. + + **Access:** Organization and Personal. + Organization tokens require `hostUserId`. Personal tokens default to the + token owner; naming a different host returns `403` `access_mode_not_supported`. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. Must not be a workspace member. + + host_user_id : typing.Optional[str] + Host member. UUID or member email — the same convention as + [`group.create`](https://developer.ro.am/docs/api/group-create) `members[].userId`. + Required for organization tokens. Optional for personal tokens + (defaults to the token owner). + + visit_permission : typing.Optional[bool] + Whether the guest may visit the host on the map. Defaults to + `true`. Ignored on an idempotent retry of an existing grant. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GuestBadge + Badge created, or the existing grant returned. + + Examples + -------- + from roamhq import RoamClient + + client = RoamClient( + roam_version="YOUR_ROAM_VERSION", + token="YOUR_TOKEN", + ) + client.guest_badges.guest_badge_create( + email="alex@client.example", + host_user_id="3f1c0b2a-8d4e-4c91-9a7b-2e6f1d8c0a11", + ) + """ + _response = self._raw_client.guest_badge_create( + email=email, host_user_id=host_user_id, visit_permission=visit_permission, request_options=request_options + ) + return _response.data + + def guest_badge_revoke( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> GuestBadgeRevokeResponse: + """ + Revoke Guest Badge(s) for an email. + + If only one host in the workspace has granted this email, `hostUserId` may + be omitted. If several hosts have, pass `hostUserId` to pick which grant + to revoke (`400` `missing_parameter` otherwise). Same-host alias rows are + all revoked together. + + Returns `{ "revoked": true }` when a matching grant was found and deleted, + or `{ "revoked": false }` when there was nothing to revoke (already gone, + including after the host was archived — archiving a member deletes the + badges they granted). Naming an archived host does not 404. + + Personal tokens can only revoke badges they issued. Naming another host is + `403` `access_mode_not_supported`; omitting `hostUserId` when only another + host granted the email is a no-op (`revoked: false`). + + **Access:** Organization and Personal. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. + + host_user_id : typing.Optional[str] + Host member. UUID or member email. Required when more than one + host has granted this email. Optional for a unique grant, and + for personal tokens (defaults to the token owner). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GuestBadgeRevokeResponse + Revoke attempted. `revoked` is true only when a matching grant was deleted. + + Examples + -------- + from roamhq import RoamClient + + client = RoamClient( + roam_version="YOUR_ROAM_VERSION", + token="YOUR_TOKEN", + ) + client.guest_badges.guest_badge_revoke( + email="alex@client.example", + ) + """ + _response = self._raw_client.guest_badge_revoke( + email=email, host_user_id=host_user_id, request_options=request_options + ) + return _response.data + + +class AsyncGuestBadgesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawGuestBadgesClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawGuestBadgesClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawGuestBadgesClient + """ + return self._raw_client + + async def guest_badge_create( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + visit_permission: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> GuestBadge: + """ + Grant a Guest Badge so an email that is **not** a workspace member can + visit a host in Roam. + + This is **not** an [On-Air event guest](https://developer.ro.am/docs/onair-api/onair-api). It is + also **not** implied by [`group.add`](https://developer.ro.am/docs/api/group-add): adding an email + to a group does not mint a badge or send the invite. Typical onboarding is + `guest.badge.create` then `group.add`. + + Repeating create for the same host and email returns the existing badge + (`visitPermission` is **not** updated) and does not re-send the invite. + + **Access:** Organization and Personal. + Organization tokens require `hostUserId`. Personal tokens default to the + token owner; naming a different host returns `403` `access_mode_not_supported`. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. Must not be a workspace member. + + host_user_id : typing.Optional[str] + Host member. UUID or member email — the same convention as + [`group.create`](https://developer.ro.am/docs/api/group-create) `members[].userId`. + Required for organization tokens. Optional for personal tokens + (defaults to the token owner). + + visit_permission : typing.Optional[bool] + Whether the guest may visit the host on the map. Defaults to + `true`. Ignored on an idempotent retry of an existing grant. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GuestBadge + Badge created, or the existing grant returned. + + Examples + -------- + import asyncio + + from roamhq import AsyncRoamClient + + client = AsyncRoamClient( + roam_version="YOUR_ROAM_VERSION", + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.guest_badges.guest_badge_create( + email="alex@client.example", + host_user_id="3f1c0b2a-8d4e-4c91-9a7b-2e6f1d8c0a11", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.guest_badge_create( + email=email, host_user_id=host_user_id, visit_permission=visit_permission, request_options=request_options + ) + return _response.data + + async def guest_badge_revoke( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> GuestBadgeRevokeResponse: + """ + Revoke Guest Badge(s) for an email. + + If only one host in the workspace has granted this email, `hostUserId` may + be omitted. If several hosts have, pass `hostUserId` to pick which grant + to revoke (`400` `missing_parameter` otherwise). Same-host alias rows are + all revoked together. + + Returns `{ "revoked": true }` when a matching grant was found and deleted, + or `{ "revoked": false }` when there was nothing to revoke (already gone, + including after the host was archived — archiving a member deletes the + badges they granted). Naming an archived host does not 404. + + Personal tokens can only revoke badges they issued. Naming another host is + `403` `access_mode_not_supported`; omitting `hostUserId` when only another + host granted the email is a no-op (`revoked: false`). + + **Access:** Organization and Personal. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. + + host_user_id : typing.Optional[str] + Host member. UUID or member email. Required when more than one + host has granted this email. Optional for a unique grant, and + for personal tokens (defaults to the token owner). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + GuestBadgeRevokeResponse + Revoke attempted. `revoked` is true only when a matching grant was deleted. + + Examples + -------- + import asyncio + + from roamhq import AsyncRoamClient + + client = AsyncRoamClient( + roam_version="YOUR_ROAM_VERSION", + token="YOUR_TOKEN", + ) + + + async def main() -> None: + await client.guest_badges.guest_badge_revoke( + email="alex@client.example", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.guest_badge_revoke( + email=email, host_user_id=host_user_id, request_options=request_options + ) + return _response.data diff --git a/src/roamhq/guest_badges/raw_client.py b/src/roamhq/guest_badges/raw_client.py new file mode 100644 index 0000000..29cee02 --- /dev/null +++ b/src/roamhq/guest_badges/raw_client.py @@ -0,0 +1,677 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing +from json.decoder import JSONDecodeError + +from ..core.api_error import ApiError +from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ..core.http_response import AsyncHttpResponse, HttpResponse +from ..core.parse_error import ParsingError +from ..core.pydantic_utilities import parse_obj_as +from ..core.request_options import RequestOptions +from ..errors.bad_request_error import BadRequestError +from ..errors.forbidden_error import ForbiddenError +from ..errors.internal_server_error import InternalServerError +from ..errors.method_not_allowed_error import MethodNotAllowedError +from ..errors.not_found_error import NotFoundError +from ..errors.too_many_requests_error import TooManyRequestsError +from ..errors.unauthorized_error import UnauthorizedError +from ..types.error import Error +from ..types.guest_badge import GuestBadge +from .types.guest_badge_revoke_response import GuestBadgeRevokeResponse +from pydantic import ValidationError + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawGuestBadgesClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def guest_badge_create( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + visit_permission: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GuestBadge]: + """ + Grant a Guest Badge so an email that is **not** a workspace member can + visit a host in Roam. + + This is **not** an [On-Air event guest](https://developer.ro.am/docs/onair-api/onair-api). It is + also **not** implied by [`group.add`](https://developer.ro.am/docs/api/group-add): adding an email + to a group does not mint a badge or send the invite. Typical onboarding is + `guest.badge.create` then `group.add`. + + Repeating create for the same host and email returns the existing badge + (`visitPermission` is **not** updated) and does not re-send the invite. + + **Access:** Organization and Personal. + Organization tokens require `hostUserId`. Personal tokens default to the + token owner; naming a different host returns `403` `access_mode_not_supported`. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. Must not be a workspace member. + + host_user_id : typing.Optional[str] + Host member. UUID or member email — the same convention as + [`group.create`](https://developer.ro.am/docs/api/group-create) `members[].userId`. + Required for organization tokens. Optional for personal tokens + (defaults to the token owner). + + visit_permission : typing.Optional[bool] + Whether the guest may visit the host on the map. Defaults to + `true`. Ignored on an idempotent retry of an existing grant. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GuestBadge] + Badge created, or the existing grant returned. + """ + _response = self._client_wrapper.httpx_client.request( + "guest.badge.create", + method="POST", + json={ + "email": email, + "hostUserId": host_user_id, + "visitPermission": visit_permission, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GuestBadge, + parse_obj_as( + type_=GuestBadge, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + def guest_badge_revoke( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[GuestBadgeRevokeResponse]: + """ + Revoke Guest Badge(s) for an email. + + If only one host in the workspace has granted this email, `hostUserId` may + be omitted. If several hosts have, pass `hostUserId` to pick which grant + to revoke (`400` `missing_parameter` otherwise). Same-host alias rows are + all revoked together. + + Returns `{ "revoked": true }` when a matching grant was found and deleted, + or `{ "revoked": false }` when there was nothing to revoke (already gone, + including after the host was archived — archiving a member deletes the + badges they granted). Naming an archived host does not 404. + + Personal tokens can only revoke badges they issued. Naming another host is + `403` `access_mode_not_supported`; omitting `hostUserId` when only another + host granted the email is a no-op (`revoked: false`). + + **Access:** Organization and Personal. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. + + host_user_id : typing.Optional[str] + Host member. UUID or member email. Required when more than one + host has granted this email. Optional for a unique grant, and + for personal tokens (defaults to the token owner). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[GuestBadgeRevokeResponse] + Revoke attempted. `revoked` is true only when a matching grant was deleted. + """ + _response = self._client_wrapper.httpx_client.request( + "guest.badge.revoke", + method="POST", + json={ + "email": email, + "hostUserId": host_user_id, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GuestBadgeRevokeResponse, + parse_obj_as( + type_=GuestBadgeRevokeResponse, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + +class AsyncRawGuestBadgesClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def guest_badge_create( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + visit_permission: typing.Optional[bool] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GuestBadge]: + """ + Grant a Guest Badge so an email that is **not** a workspace member can + visit a host in Roam. + + This is **not** an [On-Air event guest](https://developer.ro.am/docs/onair-api/onair-api). It is + also **not** implied by [`group.add`](https://developer.ro.am/docs/api/group-add): adding an email + to a group does not mint a badge or send the invite. Typical onboarding is + `guest.badge.create` then `group.add`. + + Repeating create for the same host and email returns the existing badge + (`visitPermission` is **not** updated) and does not re-send the invite. + + **Access:** Organization and Personal. + Organization tokens require `hostUserId`. Personal tokens default to the + token owner; naming a different host returns `403` `access_mode_not_supported`. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. Must not be a workspace member. + + host_user_id : typing.Optional[str] + Host member. UUID or member email — the same convention as + [`group.create`](https://developer.ro.am/docs/api/group-create) `members[].userId`. + Required for organization tokens. Optional for personal tokens + (defaults to the token owner). + + visit_permission : typing.Optional[bool] + Whether the guest may visit the host on the map. Defaults to + `true`. Ignored on an idempotent retry of an existing grant. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GuestBadge] + Badge created, or the existing grant returned. + """ + _response = await self._client_wrapper.httpx_client.request( + "guest.badge.create", + method="POST", + json={ + "email": email, + "hostUserId": host_user_id, + "visitPermission": visit_permission, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GuestBadge, + parse_obj_as( + type_=GuestBadge, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) + + async def guest_badge_revoke( + self, + *, + email: str, + host_user_id: typing.Optional[str] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[GuestBadgeRevokeResponse]: + """ + Revoke Guest Badge(s) for an email. + + If only one host in the workspace has granted this email, `hostUserId` may + be omitted. If several hosts have, pass `hostUserId` to pick which grant + to revoke (`400` `missing_parameter` otherwise). Same-host alias rows are + all revoked together. + + Returns `{ "revoked": true }` when a matching grant was found and deleted, + or `{ "revoked": false }` when there was nothing to revoke (already gone, + including after the host was archived — archiving a member deletes the + badges they granted). Naming an archived host does not 404. + + Personal tokens can only revoke badges they issued. Naming another host is + `403` `access_mode_not_supported`; omitting `hostUserId` when only another + host granted the email is a no-op (`revoked: false`). + + **Access:** Organization and Personal. + + **Required scope:** `guest:write`. Personal Access Tokens use the + `pat:guests:write` group. + + See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). + + Parameters + ---------- + email : str + Guest email. ASCII only. + + host_user_id : typing.Optional[str] + Host member. UUID or member email. Required when more than one + host has granted this email. Optional for a unique grant, and + for personal tokens (defaults to the token owner). + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[GuestBadgeRevokeResponse] + Revoke attempted. `revoked` is true only when a matching grant was deleted. + """ + _response = await self._client_wrapper.httpx_client.request( + "guest.badge.revoke", + method="POST", + json={ + "email": email, + "hostUserId": host_user_id, + }, + headers={ + "content-type": "application/json", + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + GuestBadgeRevokeResponse, + parse_obj_as( + type_=GuestBadgeRevokeResponse, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise ForbiddenError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + Error, + parse_obj_as( + type_=Error, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text) + except ValidationError as e: + raise ParsingError( + status_code=_response.status_code, headers=dict(_response.headers), body=_response.json(), cause=e + ) + raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json) diff --git a/src/roamhq/guest_badges/types/__init__.py b/src/roamhq/guest_badges/types/__init__.py new file mode 100644 index 0000000..fcf9683 --- /dev/null +++ b/src/roamhq/guest_badges/types/__init__.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +from __future__ import annotations + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .guest_badge_revoke_response import GuestBadgeRevokeResponse +_dynamic_imports: typing.Dict[str, str] = {"GuestBadgeRevokeResponse": ".guest_badge_revoke_response"} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError(f"No {attr_name} found in _dynamic_imports for module name -> {__name__}") + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError(f"Failed to import {attr_name} from {module_name}: {e}") from e + except AttributeError as e: + raise AttributeError(f"Failed to get {attr_name} from {module_name}: {e}") from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = ["GuestBadgeRevokeResponse"] diff --git a/src/roamhq/guest_badges/types/guest_badge_revoke_response.py b/src/roamhq/guest_badges/types/guest_badge_revoke_response.py new file mode 100644 index 0000000..94252ed --- /dev/null +++ b/src/roamhq/guest_badges/types/guest_badge_revoke_response.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class GuestBadgeRevokeResponse(UniversalBaseModel): + revoked: bool = pydantic.Field() + """ + Whether a matching grant was deleted. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/roamhq/reference.md b/src/roamhq/reference.md index 95cc1d2..4232c6a 100644 --- a/src/roamhq/reference.md +++ b/src/roamhq/reference.md @@ -3168,6 +3168,10 @@ your own rows. See [External activity](https://developer.ro.am/docs/guides/user-activity) for display, DND, TTL, stacking, and where the indicator appears on the map. +Identify the user with `userId`: a bare UUID, tagged `U-…` ID, or +ASCII email (same convention as `group.create` members). Third-party +systems that only have an email do not need a UUID lookup first. + **Access:** Organization and Personal. Organization tokens may target any user in the workspace. Personal tokens (OAuth or PAT) may target only the token owner. @@ -3197,7 +3201,7 @@ client = RoamClient( ) client.users.user_activity_set( - user_id="0cc74785-e31e-4403-aa5e-0cc7c1897e66", + user_id="ada@example.com", external_id="justcall:call:CA123", display=UserActivityDisplay( emoji="📞", @@ -3225,8 +3229,11 @@ client.users.user_activity_set( **user_id:** `str` -Target user. Bare or tagged UUID. Personal tokens may only -pass their own user. +Target user. Bare UUID, tagged `U-…` ID, or ASCII email +(same convention as `group.create` members). Personal +tokens may only pass their own user. Does not require +`user:read.email` — email is an identifier, not a +disclosure. @@ -3370,7 +3377,7 @@ client = RoamClient( ) client.users.user_activity_clear( - user_id="0cc74785-e31e-4403-aa5e-0cc7c1897e66", + user_id="ada@example.com", external_id="justcall:call:CA123", ) @@ -3390,8 +3397,9 @@ client.users.user_activity_clear( **user_id:** `str` -Target user. Bare or tagged UUID. Personal tokens may only -pass their own user. +Target user. Bare UUID, tagged `U-…` ID, or ASCII email +(same convention as `group.create` members). Personal +tokens may only pass their own user. @@ -3491,8 +3499,9 @@ client.users.user_activity_list( **user_id:** `str` -Target user. Bare or tagged UUID. Personal tokens may only pass -their own user. +Target user. Bare UUID, tagged `U-…` ID, or ASCII email +(same convention as `group.create` members). Personal tokens +may only pass their own user. @@ -5972,6 +5981,9 @@ mode, where only admins may change settings. Otherwise, all members have that capability. Groups require at least one member. Users can be specified by user ID or email address. +Unrecognized emails are invited as group members only — they do not receive a +[Guest Badge](https://developer.ro.am/docs/guides/guest-badges) unless you also call +[`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create). **Required scope:** `group:write` @@ -6362,6 +6374,8 @@ Add one or more group members with specified roles. Members can be specified by user ID or email address. Each member must be assigned a role (member or admin). +Adding an unrecognized email does **not** grant a [Guest Badge](https://developer.ro.am/docs/guides/guest-badges). Use [`guest.badge.create`](https://developer.ro.am/docs/api/guest-badge-create) first if the person is not a workspace member. + Apps may add members to a group if one of the following conditions is true: 1. It is a public group in their Roam. 2. They are a member of the group. @@ -6702,6 +6716,229 @@ client.groups.list() + + + + +## Guest Badges +
client.guest_badges.guest_badge_create(...) -> GuestBadge +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Grant a Guest Badge so an email that is **not** a workspace member can +visit a host in Roam. + +This is **not** an [On-Air event guest](https://developer.ro.am/docs/onair-api/onair-api). It is +also **not** implied by [`group.add`](https://developer.ro.am/docs/api/group-add): adding an email +to a group does not mint a badge or send the invite. Typical onboarding is +`guest.badge.create` then `group.add`. + +Repeating create for the same host and email returns the existing badge +(`visitPermission` is **not** updated) and does not re-send the invite. + +**Access:** Organization and Personal. +Organization tokens require `hostUserId`. Personal tokens default to the +token owner; naming a different host returns `403` `access_mode_not_supported`. + +**Required scope:** `guest:write`. Personal Access Tokens use the +`pat:guests:write` group. + +See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from roamhq import RoamClient +from roamhq.environment import RoamClientEnvironment + +client = RoamClient( + token="", + environment=RoamClientEnvironment.DEFAULT, +) + +client.guest_badges.guest_badge_create( + email="alex@client.example", + host_user_id="3f1c0b2a-8d4e-4c91-9a7b-2e6f1d8c0a11", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**email:** `str` — Guest email. ASCII only. Must not be a workspace member. + +
+
+ +
+
+ +**host_user_id:** `typing.Optional[str]` + +Host member. UUID or member email — the same convention as +[`group.create`](https://developer.ro.am/docs/api/group-create) `members[].userId`. +Required for organization tokens. Optional for personal tokens +(defaults to the token owner). + +
+
+ +
+
+ +**visit_permission:** `typing.Optional[bool]` + +Whether the guest may visit the host on the map. Defaults to +`true`. Ignored on an idempotent retry of an existing grant. + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ + +
+
+
+ +
client.guest_badges.guest_badge_revoke(...) -> GuestBadgeRevokeResponse +
+
+ +#### 📝 Description + +
+
+ +
+
+ +Revoke Guest Badge(s) for an email. + +If only one host in the workspace has granted this email, `hostUserId` may +be omitted. If several hosts have, pass `hostUserId` to pick which grant +to revoke (`400` `missing_parameter` otherwise). Same-host alias rows are +all revoked together. + +Returns `{ "revoked": true }` when a matching grant was found and deleted, +or `{ "revoked": false }` when there was nothing to revoke (already gone, +including after the host was archived — archiving a member deletes the +badges they granted). Naming an archived host does not 404. + +Personal tokens can only revoke badges they issued. Naming another host is +`403` `access_mode_not_supported`; omitting `hostUserId` when only another +host granted the email is a no-op (`revoked: false`). + +**Access:** Organization and Personal. + +**Required scope:** `guest:write`. Personal Access Tokens use the +`pat:guests:write` group. + +See [Guest Badges](https://developer.ro.am/docs/guides/guest-badges). +
+
+
+
+ +#### 🔌 Usage + +
+
+ +
+
+ +```python +from roamhq import RoamClient +from roamhq.environment import RoamClientEnvironment + +client = RoamClient( + token="", + environment=RoamClientEnvironment.DEFAULT, +) + +client.guest_badges.guest_badge_revoke( + email="alex@client.example", +) + +``` +
+
+
+
+ +#### ⚙️ Parameters + +
+
+ +
+
+ +**email:** `str` — Guest email. ASCII only. + +
+
+ +
+
+ +**host_user_id:** `typing.Optional[str]` + +Host member. UUID or member email. Required when more than one +host has granted this email. Optional for a unique grant, and +for personal tokens (defaults to the token owner). + +
+
+ +
+
+ +**request_options:** `typing.Optional[RequestOptions]` — Request-specific configuration. + +
+
+
+
+ +
@@ -6958,7 +7195,21 @@ v0-only — sending them here returns `400` / `Unrecognized event`. Roam does not probe the destination URL when you subscribe — the subscription is created immediately and the first delivery is a real event. -See the [Webhooks overview](https://developer.ro.am/docs/webhooks/webhooks) for the full list of event names and their filters. +Optional `filter` limits which occurrences are delivered. Which keys are +valid depends on `event` — see that event's page and the +[Event Filters](https://developer.ro.am/docs/webhooks/webhooks#event-filters) table. Omit +`filter` to receive every occurrence. An empty object (`{}`) is rejected, +as is a filter that does not apply to the event. + +**DMs only:** + +```json +{ + "url": "https://example.com/hooks/messages", + "event": "chat.message", + "filter": { "chatType": "dm" } +} +``` **Required scope:** `webhook:write` @@ -6975,7 +7226,7 @@ See the [Webhooks overview](https://developer.ro.am/docs/webhooks/webhooks) for
```python -from roamhq import RoamClient, WebhookSubscriptionFilter +from roamhq import RoamClient from roamhq.environment import RoamClientEnvironment client = RoamClient( @@ -6986,9 +7237,6 @@ client = RoamClient( client.webhook.subscribe( url="https://example.com/hooks/messages", event="chat.message", - filter=WebhookSubscriptionFilter( - mention=True, - ), ) ``` @@ -7022,6 +7270,11 @@ client.webhook.subscribe(
**filter:** `typing.Optional[WebhookSubscriptionFilter]` + +Optional event-specific filter. Which keys are valid depends on `event` +(see the schema). Omit to receive every occurrence; `{}` and `null` are +rejected rather than treated as "omitted". Example for DMs only: +`{"chatType": "dm"}`.
diff --git a/src/roamhq/types/__init__.py b/src/roamhq/types/__init__.py index a2b425a..5bd6753 100644 --- a/src/roamhq/types/__init__.py +++ b/src/roamhq/types/__init__.py @@ -28,6 +28,7 @@ from .group_member import GroupMember from .group_member_role import GroupMemberRole from .group_type import GroupType + from .guest_badge import GuestBadge from .lobby_booking import LobbyBooking from .lobby_booking_host import LobbyBookingHost from .lobby_booking_invitee import LobbyBookingInvitee @@ -80,6 +81,7 @@ "GroupMember": ".group_member", "GroupMemberRole": ".group_member_role", "GroupType": ".group_type", + "GuestBadge": ".guest_badge", "LobbyBooking": ".lobby_booking", "LobbyBookingHost": ".lobby_booking_host", "LobbyBookingInvitee": ".lobby_booking_invitee", @@ -156,6 +158,7 @@ def __dir__(): "GroupMember", "GroupMemberRole", "GroupType", + "GuestBadge", "LobbyBooking", "LobbyBookingHost", "LobbyBookingInvitee", diff --git a/src/roamhq/types/guest_badge.py b/src/roamhq/types/guest_badge.py new file mode 100644 index 0000000..a9e2796 --- /dev/null +++ b/src/roamhq/types/guest_badge.py @@ -0,0 +1,68 @@ +# This file was auto-generated by Fern from our API Definition. + +from __future__ import annotations + +import typing + +import pydantic +import typing_extensions +from ..core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ..core.serialization import FieldMetadata + + +class GuestBadge(UniversalBaseModel): + """ + A Guest Badge granting a non-member email limited access hosted by a workspace member. + """ + + email: str = pydantic.Field() + """ + The guest's email address (lowercase). + """ + + user_id: typing_extensions.Annotated[ + typing.Optional[str], + FieldMetadata(alias="userId"), + pydantic.Field( + alias="userId", + description="The guest's chat address ID, when one has been provisioned. Omitted if\nthe address is not yet available.", + ), + ] = None + """ + The guest's chat address ID, when one has been provisioned. Omitted if + the address is not yet available. + """ + + host_user_id: typing_extensions.Annotated[ + str, + FieldMetadata(alias="hostUserId"), + pydantic.Field( + alias="hostUserId", description="The host member's user ID (always a UUID, even if you passed an email)." + ), + ] + """ + The host member's user ID (always a UUID, even if you passed an email). + """ + + visit_permission: typing_extensions.Annotated[ + bool, + FieldMetadata(alias="visitPermission"), + pydantic.Field(alias="visitPermission", description="Whether the guest may visit the host on the map."), + ] + """ + Whether the guest may visit the host on the map. + """ + + acknowledged: bool = pydantic.Field() + """ + Whether the guest has acknowledged the badge. + """ + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/roamhq/types/webhook_subscription_filter.py b/src/roamhq/types/webhook_subscription_filter.py index e47522c..f6fd231 100644 --- a/src/roamhq/types/webhook_subscription_filter.py +++ b/src/roamhq/types/webhook_subscription_filter.py @@ -14,7 +14,20 @@ class WebhookSubscriptionFilter(UniversalBaseModel): """ - Event-specific filter to limit webhook notifications. Different properties apply to different events. + Event-specific filter passed as `filter` on `/webhook.subscribe`. Omit the + field to receive every occurrence of the event. A present but empty filter is + rejected — both `{}` and `null`. + + Which properties apply depends on `event`: + + - `chat.message`: `chatType` (`dm` or `group`) and/or `mention` + - `chat.reaction`: `names` + - `meeting.ended`: `hasVideo` (`true` only) + - `onair.event.created` / `updated` / `canceled` and `onair.guest.added`: `eventId` + - `onair.guest.rsvp`: `eventId` and/or `status` + - all other events: do not accept a filter + + Example — DMs only: `{"chatType": "dm"}`. """ chat_type: typing_extensions.Annotated[ @@ -22,16 +35,21 @@ class WebhookSubscriptionFilter(UniversalBaseModel): FieldMetadata(alias="chatType"), pydantic.Field( alias="chatType", - description="For `chat.message`: restrict to direct messages (`dm`) or group messages (`group`).", + description="For `chat.message`: restrict to direct messages (`dm`, 1:1 and\nmulti-person) or group messages (`group`, including meeting channels).\nSame vocabulary as `data.chatType` on the delivered payload.", ), ] = None """ - For `chat.message`: restrict to direct messages (`dm`) or group messages (`group`). + For `chat.message`: restrict to direct messages (`dm`, 1:1 and + multi-person) or group messages (`group`, including meeting channels). + Same vocabulary as `data.chatType` on the delivered payload. """ mention: typing.Optional[bool] = pydantic.Field(default=None) """ - For `chat.message`: restrict to messages that @mention your app. + For `chat.message`: restrict to messages that @mention your app. Only + `true` constrains anything, so `{"mention": false}` on its own is + rejected like `{}`; alongside another key (`{"chatType": "dm", + "mention": false}`) it is accepted and ignored. """ names: typing.Optional[typing.List[str]] = pydantic.Field(default=None) diff --git a/src/roamhq/users/client.py b/src/roamhq/users/client.py index b39356a..2b6529e 100644 --- a/src/roamhq/users/client.py +++ b/src/roamhq/users/client.py @@ -62,6 +62,10 @@ def user_activity_set( See [External activity](https://developer.ro.am/docs/guides/user-activity) for display, DND, TTL, stacking, and where the indicator appears on the map. + Identify the user with `userId`: a bare UUID, tagged `U-…` ID, or + ASCII email (same convention as `group.create` members). Third-party + systems that only have an email do not need a UUID lookup first. + **Access:** Organization and Personal. Organization tokens may target any user in the workspace. Personal tokens (OAuth or PAT) may target only the token owner. @@ -72,8 +76,11 @@ def user_activity_set( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. Does not require + `user:read.email` — email is an identifier, not a + disclosure. external_id : str Caller-chosen session id, unique per integration and user. @@ -124,7 +131,7 @@ def user_activity_set( token="YOUR_TOKEN", ) client.users.user_activity_set( - user_id="0cc74785-e31e-4403-aa5e-0cc7c1897e66", + user_id="ada@example.com", external_id="justcall:call:CA123", display=UserActivityDisplay( emoji="📞", @@ -173,8 +180,9 @@ def user_activity_clear( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. external_id : str The `externalId` previously passed to `user.activity.set`. @@ -195,7 +203,7 @@ def user_activity_clear( token="YOUR_TOKEN", ) client.users.user_activity_clear( - user_id="0cc74785-e31e-4403-aa5e-0cc7c1897e66", + user_id="ada@example.com", external_id="justcall:call:CA123", ) """ @@ -230,8 +238,9 @@ def user_activity_list( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only pass - their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal tokens + may only pass their own user. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -408,6 +417,10 @@ async def user_activity_set( See [External activity](https://developer.ro.am/docs/guides/user-activity) for display, DND, TTL, stacking, and where the indicator appears on the map. + Identify the user with `userId`: a bare UUID, tagged `U-…` ID, or + ASCII email (same convention as `group.create` members). Third-party + systems that only have an email do not need a UUID lookup first. + **Access:** Organization and Personal. Organization tokens may target any user in the workspace. Personal tokens (OAuth or PAT) may target only the token owner. @@ -418,8 +431,11 @@ async def user_activity_set( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. Does not require + `user:read.email` — email is an identifier, not a + disclosure. external_id : str Caller-chosen session id, unique per integration and user. @@ -475,7 +491,7 @@ async def user_activity_set( async def main() -> None: await client.users.user_activity_set( - user_id="0cc74785-e31e-4403-aa5e-0cc7c1897e66", + user_id="ada@example.com", external_id="justcall:call:CA123", display=UserActivityDisplay( emoji="📞", @@ -527,8 +543,9 @@ async def user_activity_clear( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. external_id : str The `externalId` previously passed to `user.activity.set`. @@ -554,7 +571,7 @@ async def user_activity_clear( async def main() -> None: await client.users.user_activity_clear( - user_id="0cc74785-e31e-4403-aa5e-0cc7c1897e66", + user_id="ada@example.com", external_id="justcall:call:CA123", ) @@ -592,8 +609,9 @@ async def user_activity_list( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only pass - their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal tokens + may only pass their own user. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/src/roamhq/users/raw_client.py b/src/roamhq/users/raw_client.py index e62b605..3ad388f 100644 --- a/src/roamhq/users/raw_client.py +++ b/src/roamhq/users/raw_client.py @@ -65,6 +65,10 @@ def user_activity_set( See [External activity](https://developer.ro.am/docs/guides/user-activity) for display, DND, TTL, stacking, and where the indicator appears on the map. + Identify the user with `userId`: a bare UUID, tagged `U-…` ID, or + ASCII email (same convention as `group.create` members). Third-party + systems that only have an email do not need a UUID lookup first. + **Access:** Organization and Personal. Organization tokens may target any user in the workspace. Personal tokens (OAuth or PAT) may target only the token owner. @@ -75,8 +79,11 @@ def user_activity_set( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. Does not require + `user:read.email` — email is an identifier, not a + disclosure. external_id : str Caller-chosen session id, unique per integration and user. @@ -259,8 +266,9 @@ def user_activity_clear( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. external_id : str The `externalId` previously passed to `user.activity.set`. @@ -400,8 +408,9 @@ def user_activity_list( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only pass - their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal tokens + may only pass their own user. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -729,6 +738,10 @@ async def user_activity_set( See [External activity](https://developer.ro.am/docs/guides/user-activity) for display, DND, TTL, stacking, and where the indicator appears on the map. + Identify the user with `userId`: a bare UUID, tagged `U-…` ID, or + ASCII email (same convention as `group.create` members). Third-party + systems that only have an email do not need a UUID lookup first. + **Access:** Organization and Personal. Organization tokens may target any user in the workspace. Personal tokens (OAuth or PAT) may target only the token owner. @@ -739,8 +752,11 @@ async def user_activity_set( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. Does not require + `user:read.email` — email is an identifier, not a + disclosure. external_id : str Caller-chosen session id, unique per integration and user. @@ -923,8 +939,9 @@ async def user_activity_clear( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only - pass their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal + tokens may only pass their own user. external_id : str The `externalId` previously passed to `user.activity.set`. @@ -1064,8 +1081,9 @@ async def user_activity_list( Parameters ---------- user_id : str - Target user. Bare or tagged UUID. Personal tokens may only pass - their own user. + Target user. Bare UUID, tagged `U-…` ID, or ASCII email + (same convention as `group.create` members). Personal tokens + may only pass their own user. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/src/roamhq/webhook/client.py b/src/roamhq/webhook/client.py index 6271995..b2a2c7c 100644 --- a/src/roamhq/webhook/client.py +++ b/src/roamhq/webhook/client.py @@ -92,7 +92,21 @@ def subscribe( Roam does not probe the destination URL when you subscribe — the subscription is created immediately and the first delivery is a real event. - See the [Webhooks overview](https://developer.ro.am/docs/webhooks/webhooks) for the full list of event names and their filters. + Optional `filter` limits which occurrences are delivered. Which keys are + valid depends on `event` — see that event's page and the + [Event Filters](https://developer.ro.am/docs/webhooks/webhooks#event-filters) table. Omit + `filter` to receive every occurrence. An empty object (`{}`) is rejected, + as is a filter that does not apply to the event. + + **DMs only:** + + ```json + { + "url": "https://example.com/hooks/messages", + "event": "chat.message", + "filter": { "chatType": "dm" } + } + ``` **Required scope:** `webhook:write` @@ -105,6 +119,10 @@ def subscribe( Event to subscribe to. filter : typing.Optional[WebhookSubscriptionFilter] + Optional event-specific filter. Which keys are valid depends on `event` + (see the schema). Omit to receive every occurrence; `{}` and `null` are + rejected rather than treated as "omitted". Example for DMs only: + `{"chatType": "dm"}`. api_version : typing.Optional[str] Optional [API version](https://developer.ro.am/docs/guides/api-versioning) (`YYYY-MM-DD`) to pin @@ -122,7 +140,7 @@ def subscribe( Examples -------- - from roamhq import RoamClient, WebhookSubscriptionFilter + from roamhq import RoamClient client = RoamClient( roam_version="YOUR_ROAM_VERSION", @@ -131,9 +149,6 @@ def subscribe( client.webhook.subscribe( url="https://example.com/hooks/messages", event="chat.message", - filter=WebhookSubscriptionFilter( - mention=True, - ), ) """ _response = self._raw_client.subscribe( @@ -342,7 +357,21 @@ async def subscribe( Roam does not probe the destination URL when you subscribe — the subscription is created immediately and the first delivery is a real event. - See the [Webhooks overview](https://developer.ro.am/docs/webhooks/webhooks) for the full list of event names and their filters. + Optional `filter` limits which occurrences are delivered. Which keys are + valid depends on `event` — see that event's page and the + [Event Filters](https://developer.ro.am/docs/webhooks/webhooks#event-filters) table. Omit + `filter` to receive every occurrence. An empty object (`{}`) is rejected, + as is a filter that does not apply to the event. + + **DMs only:** + + ```json + { + "url": "https://example.com/hooks/messages", + "event": "chat.message", + "filter": { "chatType": "dm" } + } + ``` **Required scope:** `webhook:write` @@ -355,6 +384,10 @@ async def subscribe( Event to subscribe to. filter : typing.Optional[WebhookSubscriptionFilter] + Optional event-specific filter. Which keys are valid depends on `event` + (see the schema). Omit to receive every occurrence; `{}` and `null` are + rejected rather than treated as "omitted". Example for DMs only: + `{"chatType": "dm"}`. api_version : typing.Optional[str] Optional [API version](https://developer.ro.am/docs/guides/api-versioning) (`YYYY-MM-DD`) to pin @@ -374,7 +407,7 @@ async def subscribe( -------- import asyncio - from roamhq import AsyncRoamClient, WebhookSubscriptionFilter + from roamhq import AsyncRoamClient client = AsyncRoamClient( roam_version="YOUR_ROAM_VERSION", @@ -386,9 +419,6 @@ async def main() -> None: await client.webhook.subscribe( url="https://example.com/hooks/messages", event="chat.message", - filter=WebhookSubscriptionFilter( - mention=True, - ), ) diff --git a/src/roamhq/webhook/raw_client.py b/src/roamhq/webhook/raw_client.py index 1b4a54a..046abf6 100644 --- a/src/roamhq/webhook/raw_client.py +++ b/src/roamhq/webhook/raw_client.py @@ -137,7 +137,21 @@ def subscribe( Roam does not probe the destination URL when you subscribe — the subscription is created immediately and the first delivery is a real event. - See the [Webhooks overview](https://developer.ro.am/docs/webhooks/webhooks) for the full list of event names and their filters. + Optional `filter` limits which occurrences are delivered. Which keys are + valid depends on `event` — see that event's page and the + [Event Filters](https://developer.ro.am/docs/webhooks/webhooks#event-filters) table. Omit + `filter` to receive every occurrence. An empty object (`{}`) is rejected, + as is a filter that does not apply to the event. + + **DMs only:** + + ```json + { + "url": "https://example.com/hooks/messages", + "event": "chat.message", + "filter": { "chatType": "dm" } + } + ``` **Required scope:** `webhook:write` @@ -150,6 +164,10 @@ def subscribe( Event to subscribe to. filter : typing.Optional[WebhookSubscriptionFilter] + Optional event-specific filter. Which keys are valid depends on `event` + (see the schema). Omit to receive every occurrence; `{}` and `null` are + rejected rather than treated as "omitted". Example for DMs only: + `{"chatType": "dm"}`. api_version : typing.Optional[str] Optional [API version](https://developer.ro.am/docs/guides/api-versioning) (`YYYY-MM-DD`) to pin @@ -172,7 +190,7 @@ def subscribe( "url": url, "event": event, "filter": convert_and_respect_annotation_metadata( - object_=filter, annotation=typing.Optional[WebhookSubscriptionFilter], direction="write" + object_=filter, annotation=WebhookSubscriptionFilter, direction="write" ), "apiVersion": api_version, }, @@ -592,7 +610,21 @@ async def subscribe( Roam does not probe the destination URL when you subscribe — the subscription is created immediately and the first delivery is a real event. - See the [Webhooks overview](https://developer.ro.am/docs/webhooks/webhooks) for the full list of event names and their filters. + Optional `filter` limits which occurrences are delivered. Which keys are + valid depends on `event` — see that event's page and the + [Event Filters](https://developer.ro.am/docs/webhooks/webhooks#event-filters) table. Omit + `filter` to receive every occurrence. An empty object (`{}`) is rejected, + as is a filter that does not apply to the event. + + **DMs only:** + + ```json + { + "url": "https://example.com/hooks/messages", + "event": "chat.message", + "filter": { "chatType": "dm" } + } + ``` **Required scope:** `webhook:write` @@ -605,6 +637,10 @@ async def subscribe( Event to subscribe to. filter : typing.Optional[WebhookSubscriptionFilter] + Optional event-specific filter. Which keys are valid depends on `event` + (see the schema). Omit to receive every occurrence; `{}` and `null` are + rejected rather than treated as "omitted". Example for DMs only: + `{"chatType": "dm"}`. api_version : typing.Optional[str] Optional [API version](https://developer.ro.am/docs/guides/api-versioning) (`YYYY-MM-DD`) to pin @@ -627,7 +663,7 @@ async def subscribe( "url": url, "event": event, "filter": convert_and_respect_annotation_metadata( - object_=filter, annotation=typing.Optional[WebhookSubscriptionFilter], direction="write" + object_=filter, annotation=WebhookSubscriptionFilter, direction="write" ), "apiVersion": api_version, },