vortex-python-sdk 0.1.3__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.0] - 2026-01-23
11
+
12
+ ### Added
13
+ - **Internal Invitations**: New `'internal'` delivery type for customer-managed invitations
14
+ - Allows creating invitations with `delivery_types: ['internal']`
15
+ - No email/SMS communication triggered by Vortex
16
+ - Target value can be any customer-defined identifier
17
+ - Useful for in-app invitation flows managed by customer's application
18
+
19
+ ### Changed
20
+ - Updated `delivery_types` type to include `'internal'` as a valid Literal value
21
+
10
22
  ## [0.1.3] - 2025-01-29
11
23
 
12
24
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-python-sdk
3
- Version: 0.1.3
3
+ Version: 0.3.0
4
4
  Summary: Vortex Python SDK for invitation management and JWT generation
5
5
  Author-email: TeamVortexSoftware <support@vortexsoftware.com>
6
6
  License-Expression: MIT
@@ -40,6 +40,21 @@ Dynamic: license-file
40
40
 
41
41
  A Python SDK for Vortex invitation management and JWT generation.
42
42
 
43
+ ## Features
44
+
45
+ ### Invitation Delivery Types
46
+
47
+ Vortex supports multiple delivery methods for invitations:
48
+
49
+ - **`email`** - Email invitations sent by Vortex (includes reminders and nudges)
50
+ - **`phone`** - Phone invitations sent by the user/customer
51
+ - **`share`** - Shareable invitation links for social sharing
52
+ - **`internal`** - Internal invitations managed entirely by your application
53
+ - No email/SMS communication triggered by Vortex
54
+ - Target value can be any customer-defined identifier
55
+ - Useful for in-app invitation flows where you handle the delivery
56
+ - Example use case: In-app notifications, dashboard invites, etc.
57
+
43
58
  ## Installation
44
59
 
45
60
  ```bash
@@ -2,6 +2,21 @@
2
2
 
3
3
  A Python SDK for Vortex invitation management and JWT generation.
4
4
 
5
+ ## Features
6
+
7
+ ### Invitation Delivery Types
8
+
9
+ Vortex supports multiple delivery methods for invitations:
10
+
11
+ - **`email`** - Email invitations sent by Vortex (includes reminders and nudges)
12
+ - **`phone`** - Phone invitations sent by the user/customer
13
+ - **`share`** - Shareable invitation links for social sharing
14
+ - **`internal`** - Internal invitations managed entirely by your application
15
+ - No email/SMS communication triggered by Vortex
16
+ - Target value can be any customer-defined identifier
17
+ - Useful for in-app invitation flows where you handle the delivery
18
+ - Example use case: In-app notifications, dashboard invites, etc.
19
+
5
20
  ## Installation
6
21
 
7
22
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vortex-python-sdk"
7
- version = "0.1.3"
7
+ version = "0.3.0"
8
8
  description = "Vortex Python SDK for invitation management and JWT generation"
9
9
  authors = [{name = "TeamVortexSoftware", email = "support@vortexsoftware.com"}]
10
10
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex-python-sdk
3
- Version: 0.1.3
3
+ Version: 0.3.0
4
4
  Summary: Vortex Python SDK for invitation management and JWT generation
5
5
  Author-email: TeamVortexSoftware <support@vortexsoftware.com>
6
6
  License-Expression: MIT
@@ -40,6 +40,21 @@ Dynamic: license-file
40
40
 
41
41
  A Python SDK for Vortex invitation management and JWT generation.
42
42
 
43
+ ## Features
44
+
45
+ ### Invitation Delivery Types
46
+
47
+ Vortex supports multiple delivery methods for invitations:
48
+
49
+ - **`email`** - Email invitations sent by Vortex (includes reminders and nudges)
50
+ - **`phone`** - Phone invitations sent by the user/customer
51
+ - **`share`** - Shareable invitation links for social sharing
52
+ - **`internal`** - Internal invitations managed entirely by your application
53
+ - No email/SMS communication triggered by Vortex
54
+ - Target value can be any customer-defined identifier
55
+ - Useful for in-app invitation flows where you handle the delivery
56
+ - Example use case: In-app notifications, dashboard invites, etc.
57
+
43
58
  ## Installation
44
59
 
45
60
  ```bash
@@ -6,7 +6,7 @@ from pydantic import BaseModel, Field
6
6
  class IdentifierInput(BaseModel):
7
7
  """Identifier structure for JWT generation"""
8
8
 
9
- type: Literal["email", "sms"]
9
+ type: Literal["email", "phone"]
10
10
  value: str
11
11
 
12
12
 
@@ -61,12 +61,16 @@ class User(BaseModel):
61
61
  - email: User's email address
62
62
 
63
63
  Optional fields:
64
+ - name: User's display name
65
+ - avatar_url: User's avatar URL (must be HTTPS, max 2000 chars)
64
66
  - admin_scopes: List of admin scopes (e.g., ['autojoin'])
65
67
 
66
68
  Additional fields are allowed via extra parameter
67
69
  """
68
70
  id: str
69
71
  email: str
72
+ name: Optional[str] = None
73
+ avatar_url: Optional[str] = None
70
74
  admin_scopes: Optional[List[str]] = None
71
75
 
72
76
  class Config:
@@ -125,7 +129,7 @@ class JwtPayload(BaseModel):
125
129
 
126
130
 
127
131
  class InvitationTarget(BaseModel):
128
- type: Literal["email", "sms"]
132
+ type: Literal["email", "phone"]
129
133
  value: str
130
134
 
131
135
 
@@ -181,7 +185,7 @@ class InvitationResult(BaseModel):
181
185
  created_at: str = Field(alias="createdAt")
182
186
  deactivated: bool
183
187
  delivery_count: int = Field(alias="deliveryCount")
184
- delivery_types: List[Literal["email", "sms", "share"]] = Field(
188
+ delivery_types: List[Literal["email", "phone", "share", "internal"]] = Field(
185
189
  alias="deliveryTypes"
186
190
  )
187
191
  foreign_creator_id: str = Field(alias="foreignCreatorId")
@@ -209,6 +213,9 @@ class InvitationResult(BaseModel):
209
213
  expires: Optional[str] = None
210
214
  metadata: Optional[Dict[str, Any]] = None
211
215
  pass_through: Optional[str] = Field(None, alias="passThrough")
216
+ source: Optional[str] = None
217
+ creator_name: Optional[str] = Field(None, alias="creatorName")
218
+ creator_avatar_url: Optional[str] = Field(None, alias="creatorAvatarUrl")
212
219
 
213
220
  class Config:
214
221
  populate_by_name = True
@@ -258,3 +265,69 @@ ApiResponseJson = Union[
258
265
  InvitationResult, Dict[str, List[InvitationResult]], Dict[str, Any]
259
266
  ]
260
267
  ApiRequestBody = Union[AcceptInvitationRequest, None]
268
+
269
+
270
+ # --- Types for creating invitations via backend API ---
271
+
272
+ class CreateInvitationTargetType(str):
273
+ """Target types for creating invitations"""
274
+ EMAIL = "email"
275
+ PHONE = "phone"
276
+ INTERNAL = "internal"
277
+
278
+
279
+ class CreateInvitationTarget(BaseModel):
280
+ """Target for creating an invitation"""
281
+ type: Literal["email", "phone", "internal"]
282
+ value: str
283
+
284
+
285
+ class Inviter(BaseModel):
286
+ """
287
+ Information about the user creating the invitation (the inviter).
288
+ This is equivalent to what would be extracted from a JWT in client-side flows.
289
+ """
290
+ user_id: str = Field(alias="userId")
291
+ user_email: Optional[str] = Field(None, alias="userEmail")
292
+ name: Optional[str] = None
293
+ avatar_url: Optional[str] = Field(None, alias="avatarUrl")
294
+
295
+ class Config:
296
+ populate_by_name = True
297
+
298
+
299
+ class CreateInvitationGroup(BaseModel):
300
+ """Group information for creating invitations"""
301
+ type: str
302
+ group_id: str = Field(alias="groupId")
303
+ name: str
304
+
305
+ class Config:
306
+ populate_by_name = True
307
+
308
+
309
+ class BackendCreateInvitationRequest(BaseModel):
310
+ """
311
+ Request body for creating an invitation via the public API (backend SDK use).
312
+ """
313
+ widget_configuration_id: str = Field(alias="widgetConfigurationId")
314
+ target: CreateInvitationTarget
315
+ inviter: Inviter
316
+ groups: Optional[List[CreateInvitationGroup]] = None
317
+ source: Optional[str] = None
318
+ template_variables: Optional[Dict[str, str]] = Field(None, alias="templateVariables")
319
+ metadata: Optional[Dict[str, Any]] = None
320
+
321
+ class Config:
322
+ populate_by_name = True
323
+
324
+
325
+ class CreateInvitationResponse(BaseModel):
326
+ """Response from creating an invitation"""
327
+ id: str
328
+ short_link: str = Field(alias="shortLink")
329
+ status: str
330
+ created_at: str = Field(alias="createdAt")
331
+
332
+ class Config:
333
+ populate_by_name = True
@@ -13,8 +13,13 @@ logger = logging.getLogger(__name__)
13
13
 
14
14
  from .types import (
15
15
  AcceptUser,
16
+ BackendCreateInvitationRequest,
17
+ CreateInvitationGroup,
18
+ CreateInvitationResponse,
19
+ CreateInvitationTarget,
16
20
  Invitation,
17
21
  InvitationTarget,
22
+ Inviter,
18
23
  User,
19
24
  VortexApiError,
20
25
  )
@@ -48,7 +53,8 @@ class Vortex:
48
53
  Generate a JWT token for a user
49
54
 
50
55
  Args:
51
- user: User object or dict with 'id', 'email', and optional 'admin_scopes'
56
+ user: User object or dict with 'id', 'email', and optional 'name',
57
+ 'avatar_url', 'admin_scopes'
52
58
  **extra: Additional properties to include in JWT payload
53
59
 
54
60
  Returns:
@@ -61,7 +67,13 @@ class Vortex:
61
67
  user = {'id': 'user-123', 'email': 'user@example.com', 'admin_scopes': ['autojoin']}
62
68
  jwt = vortex.generate_jwt(user=user)
63
69
 
64
- # With additional properties
70
+ # With additional properties including name and avatar
71
+ user = {
72
+ 'id': 'user-123',
73
+ 'email': 'user@example.com',
74
+ 'name': 'John Doe',
75
+ 'avatar_url': 'https://example.com/avatar.jpg'
76
+ }
65
77
  jwt = vortex.generate_jwt(user=user, role='admin', department='Engineering')
66
78
  """
67
79
  # Convert dict to User if needed
@@ -114,6 +126,14 @@ class Vortex:
114
126
  "expires": expires,
115
127
  }
116
128
 
129
+ # Add name if present (convert snake_case to camelCase for JWT)
130
+ if user.name:
131
+ jwt_payload["name"] = user.name
132
+
133
+ # Add avatarUrl if present (convert snake_case to camelCase for JWT)
134
+ if user.avatar_url:
135
+ jwt_payload["avatarUrl"] = user.avatar_url
136
+
117
137
  # Add adminScopes if present
118
138
  if user.admin_scopes:
119
139
  jwt_payload["adminScopes"] = user.admin_scopes
@@ -399,7 +419,7 @@ class Vortex:
399
419
  user = AcceptUser()
400
420
  if target_type == "email":
401
421
  user.email = target_value
402
- elif target_type in ("sms", "phoneNumber"):
422
+ elif target_type in ("phone", "phoneNumber"):
403
423
  user.phone = target_value
404
424
  else:
405
425
  # For other types, try to use as email
@@ -492,7 +512,7 @@ class Vortex:
492
512
  user = AcceptUser()
493
513
  if target_type == "email":
494
514
  user.email = target_value
495
- elif target_type in ("sms", "phoneNumber"):
515
+ elif target_type in ("phone", "phoneNumber"):
496
516
  user.phone = target_value
497
517
  else:
498
518
  # For other types, try to use as email
@@ -635,6 +655,132 @@ class Vortex:
635
655
  )
636
656
  return Invitation(**response)
637
657
 
658
+ async def create_invitation(
659
+ self,
660
+ widget_configuration_id: str,
661
+ target: Union[CreateInvitationTarget, Dict[str, str]],
662
+ inviter: Union[Inviter, Dict[str, str]],
663
+ groups: Optional[List[Union[CreateInvitationGroup, Dict[str, str]]]] = None,
664
+ source: Optional[str] = None,
665
+ template_variables: Optional[Dict[str, str]] = None,
666
+ metadata: Optional[Dict[str, Any]] = None,
667
+ ) -> CreateInvitationResponse:
668
+ """
669
+ Create an invitation from your backend.
670
+
671
+ This method allows you to create invitations programmatically using your API key,
672
+ without requiring a user JWT token. Useful for server-side invitation creation,
673
+ such as "People You May Know" flows or admin-initiated invitations.
674
+
675
+ Args:
676
+ widget_configuration_id: The widget configuration ID to use
677
+ target: The target of the invitation (who is being invited)
678
+ - type: 'email', 'phone', or 'internal'
679
+ - value: Email address, phone number, or internal user ID
680
+ inviter: Information about the user creating the invitation
681
+ - user_id: Your internal user ID for the inviter (required)
682
+ - user_email: Optional email of the inviter
683
+ - name: Optional display name of the inviter
684
+ groups: Optional groups/scopes to associate with the invitation
685
+ source: Optional source for analytics (defaults to 'api')
686
+ template_variables: Optional template variables for email customization
687
+ metadata: Optional metadata passed through to webhooks
688
+
689
+ Returns:
690
+ CreateInvitationResponse with id, short_link, status, and created_at
691
+
692
+ Example:
693
+ # Create an email invitation
694
+ result = await vortex.create_invitation(
695
+ widget_configuration_id="widget-config-123",
696
+ target={"type": "email", "value": "invitee@example.com"},
697
+ inviter={"user_id": "user-456", "user_email": "inviter@example.com"},
698
+ groups=[{"type": "team", "group_id": "team-789", "name": "Engineering"}],
699
+ )
700
+
701
+ # Create an internal invitation (PYMK flow - no email sent)
702
+ result = await vortex.create_invitation(
703
+ widget_configuration_id="widget-config-123",
704
+ target={"type": "internal", "value": "internal-user-abc"},
705
+ inviter={"user_id": "user-456"},
706
+ source="pymk",
707
+ )
708
+ """
709
+ # Convert dicts to models if needed
710
+ if isinstance(target, dict):
711
+ target = CreateInvitationTarget(**target)
712
+ if isinstance(inviter, dict):
713
+ inviter = Inviter(**inviter)
714
+ if groups:
715
+ groups = [
716
+ CreateInvitationGroup(**g) if isinstance(g, dict) else g
717
+ for g in groups
718
+ ]
719
+
720
+ request = BackendCreateInvitationRequest(
721
+ widget_configuration_id=widget_configuration_id,
722
+ target=target,
723
+ inviter=inviter,
724
+ groups=groups,
725
+ source=source,
726
+ template_variables=template_variables,
727
+ metadata=metadata,
728
+ )
729
+
730
+ # Use by_alias=True to get camelCase keys for the API
731
+ response = await self._vortex_api_request(
732
+ "POST", "/invitations", data=request.model_dump(by_alias=True, exclude_none=True)
733
+ )
734
+ return CreateInvitationResponse(**response)
735
+
736
+ def create_invitation_sync(
737
+ self,
738
+ widget_configuration_id: str,
739
+ target: Union[CreateInvitationTarget, Dict[str, str]],
740
+ inviter: Union[Inviter, Dict[str, str]],
741
+ groups: Optional[List[Union[CreateInvitationGroup, Dict[str, str]]]] = None,
742
+ source: Optional[str] = None,
743
+ template_variables: Optional[Dict[str, str]] = None,
744
+ metadata: Optional[Dict[str, Any]] = None,
745
+ ) -> CreateInvitationResponse:
746
+ """
747
+ Create an invitation from your backend (synchronous version).
748
+
749
+ See create_invitation() for full documentation.
750
+
751
+ Example:
752
+ result = vortex.create_invitation_sync(
753
+ widget_configuration_id="widget-config-123",
754
+ target={"type": "email", "value": "invitee@example.com"},
755
+ inviter={"user_id": "user-456"},
756
+ )
757
+ """
758
+ # Convert dicts to models if needed
759
+ if isinstance(target, dict):
760
+ target = CreateInvitationTarget(**target)
761
+ if isinstance(inviter, dict):
762
+ inviter = Inviter(**inviter)
763
+ if groups:
764
+ groups = [
765
+ CreateInvitationGroup(**g) if isinstance(g, dict) else g
766
+ for g in groups
767
+ ]
768
+
769
+ request = BackendCreateInvitationRequest(
770
+ widget_configuration_id=widget_configuration_id,
771
+ target=target,
772
+ inviter=inviter,
773
+ groups=groups,
774
+ source=source,
775
+ template_variables=template_variables,
776
+ metadata=metadata,
777
+ )
778
+
779
+ response = self._vortex_api_request_sync(
780
+ "POST", "/invitations", data=request.model_dump(by_alias=True, exclude_none=True)
781
+ )
782
+ return CreateInvitationResponse(**response)
783
+
638
784
  async def close(self) -> None:
639
785
  """Close the HTTP client"""
640
786
  await self._client.aclose()