mushu-sdk 0.1.2__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.
@@ -0,0 +1,39 @@
1
+ # Dependencies
2
+ node_modules/
3
+ __pycache__/
4
+ *.pyc
5
+ .venv/
6
+ venv/
7
+ *.egg-info/
8
+
9
+ # CDK
10
+ cdk.out/
11
+ *.js
12
+ !jest.config.js
13
+ *.d.ts
14
+
15
+ # IDE
16
+ .idea/
17
+ .vscode/
18
+ *.swp
19
+ *.swo
20
+
21
+ # OS
22
+ .DS_Store
23
+ Thumbs.db
24
+
25
+ # Environment
26
+ .env
27
+ .env.local
28
+ *.local
29
+
30
+ # Build
31
+ dist/
32
+ build/
33
+ *.zip
34
+ .lambda_build/
35
+
36
+ # Secrets (never commit)
37
+ *.p8
38
+ *.pem
39
+ credentials.json
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## [0.1.2](https://github.com/cjoelrun/mushu/compare/mushu-sdk-v0.1.1...mushu-sdk-v0.1.2) (2026-01-25)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **sdk:** add email-validator dependency for EmailStr ([ac57008](https://github.com/cjoelrun/mushu/commit/ac57008d1715a1851b6af89662d77125740c1986))
9
+
10
+ ## [0.1.1](https://github.com/cjoelrun/mushu/compare/mushu-sdk-v0.1.0...mushu-sdk-v0.1.1) (2026-01-25)
11
+
12
+
13
+ ### Features
14
+
15
+ * **sdk:** regenerate from OpenAPI specs ([b1c44c5](https://github.com/cjoelrun/mushu/commit/b1c44c555175769bf20a3f493395c34bf2ee60be))
16
+ * **sdk:** regenerate from OpenAPI specs ([ab73e9e](https://github.com/cjoelrun/mushu/commit/ab73e9e575074ac1a0fa554d6154c076f7bb463d))
@@ -0,0 +1,61 @@
1
+ Metadata-Version: 2.4
2
+ Name: mushu-sdk
3
+ Version: 0.1.2
4
+ Summary: Python types for Mushu API - auto-generated from OpenAPI specs
5
+ Project-URL: Homepage, https://admin.mushucorp.com
6
+ Project-URL: Documentation, https://admin.mushucorp.com/docs
7
+ Project-URL: Repository, https://github.com/mushucorp/mushu
8
+ License: MIT
9
+ Keywords: api,mushu,openapi,sdk
10
+ Requires-Python: >=3.10
11
+ Requires-Dist: pydantic[email]>=2.0.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Mushu Python SDK
15
+
16
+ Auto-generated Python types for the Mushu API.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install mushu-sdk
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```python
27
+ from mushu.media import UploadUrlRequest, MediaItem, MediaStatus
28
+ from mushu.auth import UserResponse
29
+ from mushu.notify import NotifyRequest
30
+ from mushu.pay import WalletResponse
31
+
32
+ # Type-safe request
33
+ request = UploadUrlRequest(
34
+ org_id="org_123",
35
+ filename="photo.jpg",
36
+ content_type="image/jpeg",
37
+ size_bytes=1024000,
38
+ )
39
+
40
+ # Type hints work in your IDE
41
+ def handle_media(item: MediaItem) -> str:
42
+ if item.status == MediaStatus.ready:
43
+ return item.url
44
+ return "Processing..."
45
+ ```
46
+
47
+ ## Regenerating
48
+
49
+ These types are auto-generated from OpenAPI specs. To regenerate:
50
+
51
+ ```bash
52
+ cd sdks
53
+ ./generate.sh
54
+ ```
55
+
56
+ ## Services
57
+
58
+ - **auth** - Authentication, users, organizations
59
+ - **notify** - Push notifications, email, devices
60
+ - **media** - Image and video hosting
61
+ - **pay** - Payments, wallets, billing
@@ -0,0 +1,48 @@
1
+ # Mushu Python SDK
2
+
3
+ Auto-generated Python types for the Mushu API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install mushu-sdk
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```python
14
+ from mushu.media import UploadUrlRequest, MediaItem, MediaStatus
15
+ from mushu.auth import UserResponse
16
+ from mushu.notify import NotifyRequest
17
+ from mushu.pay import WalletResponse
18
+
19
+ # Type-safe request
20
+ request = UploadUrlRequest(
21
+ org_id="org_123",
22
+ filename="photo.jpg",
23
+ content_type="image/jpeg",
24
+ size_bytes=1024000,
25
+ )
26
+
27
+ # Type hints work in your IDE
28
+ def handle_media(item: MediaItem) -> str:
29
+ if item.status == MediaStatus.ready:
30
+ return item.url
31
+ return "Processing..."
32
+ ```
33
+
34
+ ## Regenerating
35
+
36
+ These types are auto-generated from OpenAPI specs. To regenerate:
37
+
38
+ ```bash
39
+ cd sdks
40
+ ./generate.sh
41
+ ```
42
+
43
+ ## Services
44
+
45
+ - **auth** - Authentication, users, organizations
46
+ - **notify** - Push notifications, email, devices
47
+ - **media** - Image and video hosting
48
+ - **pay** - Payments, wallets, billing
@@ -0,0 +1,20 @@
1
+ """
2
+ Mushu SDK - Auto-generated Python types
3
+
4
+ Generated from OpenAPI specs at:
5
+ - https://auth.mushucorp.com/openapi.json
6
+ - https://notify.mushucorp.com/openapi.json
7
+ - https://media.mushucorp.com/openapi.json
8
+ - https://pay.mushucorp.com/openapi.json
9
+
10
+ Usage:
11
+ from mushu.media import UploadUrlRequest, MediaItem
12
+ from mushu.auth import UserResponse, OrgResponse
13
+ from mushu.notify import NotifyRequest
14
+ from mushu.pay import WalletResponse
15
+ """
16
+
17
+ from mushu import auth, media, notify, pay
18
+
19
+ __all__ = ["auth", "media", "notify", "pay"]
20
+ __version__ = "0.1.0"
@@ -0,0 +1,220 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: auth-openapi.json
3
+ # timestamp: 2026-01-25T18:54:23+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class AppleS2SRequest(BaseModel):
13
+ payload: str = Field(
14
+ ..., description='JWT containing the S2S notification', title='Payload'
15
+ )
16
+
17
+
18
+ class AppleSignInRequest(BaseModel):
19
+ id_token: str = Field(
20
+ ..., description='Apple ID token from Sign In with Apple', title='Id Token'
21
+ )
22
+ access_code: str = Field(
23
+ ..., description='Apple authorization code', title='Access Code'
24
+ )
25
+ nonce: str = Field(
26
+ ..., description='Nonce used during sign in (for validation)', title='Nonce'
27
+ )
28
+ given_name: str | None = Field(
29
+ None, description="User's given name", title='Given Name'
30
+ )
31
+ family_name: str | None = Field(
32
+ None, description="User's family name", title='Family Name'
33
+ )
34
+
35
+
36
+ class AuthProviderType(Enum):
37
+ apple = 'apple'
38
+ google = 'google'
39
+
40
+
41
+ class BodyAppleCallbackAuthAppleCallbackPost(BaseModel):
42
+ code: str = Field(..., description='Authorization code from Apple', title='Code')
43
+ id_token: str = Field(..., description='ID token from Apple', title='Id Token')
44
+ state: str = Field(..., description='OAuth state parameter', title='State')
45
+ user: str | None = Field(
46
+ None, description='User info JSON (first sign-in)', title='User'
47
+ )
48
+
49
+
50
+ class CreateAuthProviderRequest(BaseModel):
51
+ provider_type: AuthProviderType = Field(
52
+ ..., description='Provider type (apple, google)'
53
+ )
54
+ enabled: bool | None = Field(
55
+ True, description='Whether the provider is enabled', title='Enabled'
56
+ )
57
+ apple_team_id: str | None = Field(
58
+ None, description='Apple Team ID', title='Apple Team Id'
59
+ )
60
+ apple_key_id: str | None = Field(
61
+ None, description='Apple Key ID', title='Apple Key Id'
62
+ )
63
+ apple_bundle_ids: list[str] | None = Field(
64
+ None, description='iOS app bundle IDs', title='Apple Bundle Ids'
65
+ )
66
+ apple_services_id: str | None = Field(
67
+ None, description='Apple Services ID for web OAuth', title='Apple Services Id'
68
+ )
69
+ apple_private_key: str | None = Field(
70
+ None, description='Apple private key (P8 format)', title='Apple Private Key'
71
+ )
72
+ google_client_id: str | None = Field(
73
+ None, description='Google OAuth client ID', title='Google Client Id'
74
+ )
75
+ google_client_secret: str | None = Field(
76
+ None, description='Google OAuth client secret', title='Google Client Secret'
77
+ )
78
+
79
+
80
+ class HealthResponse(BaseModel):
81
+ status: str | None = Field('ok', description='Service status', title='Status')
82
+ service: str | None = Field(
83
+ 'mushu-auth', description='Service name', title='Service'
84
+ )
85
+ version: str = Field(..., description='Service version', title='Version')
86
+
87
+
88
+ class RefreshTokenRequest(BaseModel):
89
+ access_token: str = Field(
90
+ ..., description='Current access token (can be expired)', title='Access Token'
91
+ )
92
+ refresh_token: str = Field(
93
+ ..., description='Valid refresh token', title='Refresh Token'
94
+ )
95
+
96
+
97
+ class SessionTokens(BaseModel):
98
+ access_token: str = Field(..., description='JWT access token', title='Access Token')
99
+ refresh_token: str = Field(
100
+ ..., description='JWT refresh token', title='Refresh Token'
101
+ )
102
+
103
+
104
+ class UpdateAuthProviderRequest(BaseModel):
105
+ enabled: bool | None = Field(
106
+ None, description='Whether the provider is enabled', title='Enabled'
107
+ )
108
+ apple_team_id: str | None = Field(
109
+ None, description='Apple Team ID', title='Apple Team Id'
110
+ )
111
+ apple_key_id: str | None = Field(
112
+ None, description='Apple Key ID', title='Apple Key Id'
113
+ )
114
+ apple_bundle_ids: list[str] | None = Field(
115
+ None, description='iOS app bundle IDs', title='Apple Bundle Ids'
116
+ )
117
+ apple_services_id: str | None = Field(
118
+ None, description='Apple Services ID for web OAuth', title='Apple Services Id'
119
+ )
120
+ apple_private_key: str | None = Field(
121
+ None, description='Apple private key (P8 format)', title='Apple Private Key'
122
+ )
123
+ google_client_id: str | None = Field(
124
+ None, description='Google OAuth client ID', title='Google Client Id'
125
+ )
126
+ google_client_secret: str | None = Field(
127
+ None, description='Google OAuth client secret', title='Google Client Secret'
128
+ )
129
+
130
+
131
+ class UserType(Enum):
132
+ apple = 'apple'
133
+ google = 'google'
134
+
135
+
136
+ class ValidateTokenResponse(BaseModel):
137
+ valid: bool = Field(..., description='Whether the token is valid', title='Valid')
138
+ user_id: str | None = Field(
139
+ None, description='User ID if token is valid', title='User Id'
140
+ )
141
+ user_type: UserType | None = Field(None, description='User type if token is valid')
142
+ email: str | None = Field(
143
+ None, description='User email if available', title='Email'
144
+ )
145
+ app_id: str | None = Field(
146
+ None, description='App ID if token is valid', title='App Id'
147
+ )
148
+
149
+
150
+ class ValidationError(BaseModel):
151
+ loc: list[str | int] = Field(..., title='Location')
152
+ msg: str = Field(..., title='Message')
153
+ type: str = Field(..., title='Error Type')
154
+
155
+
156
+ class AuthProvider(BaseModel):
157
+ app_id: str = Field(..., description='App ID', title='App Id')
158
+ provider_type: AuthProviderType = Field(..., description='Provider type')
159
+ enabled: bool = Field(
160
+ ..., description='Whether the provider is enabled', title='Enabled'
161
+ )
162
+ created_at: str = Field(
163
+ ..., description='ISO timestamp of creation', title='Created At'
164
+ )
165
+ updated_at: str = Field(
166
+ ..., description='ISO timestamp of last update', title='Updated At'
167
+ )
168
+ is_configured: bool = Field(
169
+ ...,
170
+ description='Whether provider has minimum required config',
171
+ title='Is Configured',
172
+ )
173
+ apple_team_id: str | None = Field(
174
+ None, description='Apple Team ID', title='Apple Team Id'
175
+ )
176
+ apple_key_id: str | None = Field(
177
+ None, description='Apple Key ID', title='Apple Key Id'
178
+ )
179
+ apple_bundle_ids: list[str] | None = Field(
180
+ None, description='iOS app bundle IDs', title='Apple Bundle Ids'
181
+ )
182
+ apple_services_id: str | None = Field(
183
+ None, description='Apple Services ID for web OAuth', title='Apple Services Id'
184
+ )
185
+ google_client_id: str | None = Field(
186
+ None, description='Google OAuth client ID', title='Google Client Id'
187
+ )
188
+
189
+
190
+ class AuthProviderListResponse(BaseModel):
191
+ providers: list[AuthProvider] = Field(
192
+ ..., description='List of configured providers', title='Providers'
193
+ )
194
+
195
+
196
+ class HTTPValidationError(BaseModel):
197
+ detail: list[ValidationError] | None = Field(None, title='Detail')
198
+
199
+
200
+ class User(BaseModel):
201
+ user_id: str = Field(..., description='Unique user identifier', title='User Id')
202
+ user_type: UserType = Field(..., description='Authentication provider')
203
+ email: str | None = Field(
204
+ None, description='User email if available', title='Email'
205
+ )
206
+ email_verified: bool | None = Field(
207
+ False, description='Whether email is verified', title='Email Verified'
208
+ )
209
+ name: str | None = Field(None, description="User's display name", title='Name')
210
+ created_at: str = Field(
211
+ ..., description='ISO timestamp of account creation', title='Created At'
212
+ )
213
+ last_login: str = Field(
214
+ ..., description='ISO timestamp of last login', title='Last Login'
215
+ )
216
+
217
+
218
+ class SessionResponse(BaseModel):
219
+ user: User
220
+ tokens: SessionTokens
@@ -0,0 +1,156 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: media-openapi.json
3
+ # timestamp: 2026-01-25T18:54:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+
9
+ from pydantic import BaseModel, Field, conint
10
+
11
+
12
+ class HealthResponse(BaseModel):
13
+ status: str = Field(..., title='Status')
14
+ service: str = Field(..., title='Service')
15
+ version: str = Field(..., title='Version')
16
+
17
+
18
+ class ImageUrlsResponse(BaseModel):
19
+ media_id: str = Field(..., title='Media Id')
20
+ original: str = Field(..., description='Original image URL', title='Original')
21
+ thumbnail: str = Field(..., description='150x150 thumbnail URL', title='Thumbnail')
22
+ small: str = Field(..., description='320px width URL', title='Small')
23
+ medium: str = Field(..., description='640px width URL', title='Medium')
24
+ large: str = Field(..., description='1280px width URL', title='Large')
25
+
26
+
27
+ class MediaStatus(Enum):
28
+ pending = 'pending'
29
+ uploaded = 'uploaded'
30
+ ready = 'ready'
31
+ failed = 'failed'
32
+
33
+
34
+ class MediaType(Enum):
35
+ image = 'image'
36
+ document = 'document'
37
+ video = 'video'
38
+ other = 'other'
39
+
40
+
41
+ class UploadUrlRequest(BaseModel):
42
+ org_id: str = Field(..., description='Organization ID', title='Org Id')
43
+ app_id: str | None = Field(
44
+ None, description='App ID (optional, for app-specific media)', title='App Id'
45
+ )
46
+ filename: str = Field(..., description='Original filename', title='Filename')
47
+ content_type: str = Field(
48
+ ..., description='MIME type of the file', title='Content Type'
49
+ )
50
+ size_bytes: conint(ge=1, le=100000000) = Field(
51
+ ..., description='File size in bytes (max 100MB)', title='Size Bytes'
52
+ )
53
+
54
+
55
+ class UploadUrlResponse(BaseModel):
56
+ media_id: str = Field(..., description='Unique media ID', title='Media Id')
57
+ upload_url: str = Field(
58
+ ..., description='Presigned URL for upload', title='Upload Url'
59
+ )
60
+ expires_in: int = Field(
61
+ ..., description='Seconds until URL expires', title='Expires In'
62
+ )
63
+ key: str = Field(..., description='Storage key for the media', title='Key')
64
+
65
+
66
+ class ValidationError(BaseModel):
67
+ loc: list[str | int] = Field(..., title='Location')
68
+ msg: str = Field(..., title='Message')
69
+ type: str = Field(..., title='Error Type')
70
+
71
+
72
+ class VideoStatus(Enum):
73
+ pending = 'pending'
74
+ uploading = 'uploading'
75
+ processing = 'processing'
76
+ ready = 'ready'
77
+ error = 'error'
78
+
79
+
80
+ class VideoUploadUrlRequest(BaseModel):
81
+ org_id: str = Field(..., description='Organization ID', title='Org Id')
82
+ app_id: str | None = Field(None, description='App ID (optional)', title='App Id')
83
+ filename: str = Field(..., description='Original filename', title='Filename')
84
+ content_type: str | None = Field(
85
+ 'video/mp4',
86
+ description='MIME type (video/mp4, video/quicktime, etc.)',
87
+ title='Content Type',
88
+ )
89
+ size_bytes: conint(ge=1, le=500000000) = Field(
90
+ ..., description='File size in bytes (max 500MB)', title='Size Bytes'
91
+ )
92
+
93
+
94
+ class VideoUploadUrlResponse(BaseModel):
95
+ media_id: str = Field(..., description='Unique media ID', title='Media Id')
96
+ upload_url: str = Field(
97
+ ..., description='Presigned URL for upload to R2', title='Upload Url'
98
+ )
99
+ expires_in: int = Field(
100
+ ..., description='Seconds until URL expires', title='Expires In'
101
+ )
102
+ key: str = Field(..., description='Storage key for the video', title='Key')
103
+
104
+
105
+ class VideoVariantsResponse(BaseModel):
106
+ media_id: str = Field(..., title='Media Id')
107
+ status: VideoStatus
108
+ job_id: str | None = Field(None, description='MediaConvert job ID', title='Job Id')
109
+ original: str | None = Field(
110
+ None, description='Original uploaded video URL', title='Original'
111
+ )
112
+ optimized: str | None = Field(
113
+ None, description='H.264 MP4 (1080p, 8Mbps) URL', title='Optimized'
114
+ )
115
+ web: str | None = Field(
116
+ None, description='VP9 WebM (1080p, 5Mbps) URL', title='Web'
117
+ )
118
+ thumbnail: str | None = Field(
119
+ None, description='Thumbnail JPEG (640x360) URL', title='Thumbnail'
120
+ )
121
+ poster: str | None = Field(
122
+ None, description='Poster JPEG (1280x720) URL', title='Poster'
123
+ )
124
+ error_message: str | None = Field(
125
+ None, description='Error message if status is ERROR', title='Error Message'
126
+ )
127
+
128
+
129
+ class ConfirmUploadResponse(BaseModel):
130
+ media_id: str = Field(..., title='Media Id')
131
+ status: MediaStatus
132
+ url: str | None = Field(None, description='Public URL if ready', title='Url')
133
+
134
+
135
+ class HTTPValidationError(BaseModel):
136
+ detail: list[ValidationError] | None = Field(None, title='Detail')
137
+
138
+
139
+ class MediaItem(BaseModel):
140
+ media_id: str = Field(..., title='Media Id')
141
+ org_id: str = Field(..., title='Org Id')
142
+ app_id: str | None = Field(..., title='App Id')
143
+ filename: str = Field(..., title='Filename')
144
+ content_type: str = Field(..., title='Content Type')
145
+ size_bytes: int = Field(..., title='Size Bytes')
146
+ media_type: MediaType
147
+ status: MediaStatus
148
+ key: str = Field(..., title='Key')
149
+ created_at: str = Field(..., title='Created At')
150
+ updated_at: str = Field(..., title='Updated At')
151
+ url: str | None = Field(None, title='Url')
152
+
153
+
154
+ class MediaListResponse(BaseModel):
155
+ items: list[MediaItem] = Field(..., title='Items')
156
+ next_cursor: str | None = Field(None, title='Next Cursor')
@@ -0,0 +1,333 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: notify-openapi.json
3
+ # timestamp: 2026-01-25T18:54:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel, EmailStr, Field
11
+
12
+
13
+ class AddDomainRequest(BaseModel):
14
+ domain: str = Field(
15
+ ..., description="Domain to verify (e.g., 'acme.com')", title='Domain'
16
+ )
17
+ from_name: str | None = Field(
18
+ None, description="Default sender name (e.g., 'Acme Inc')", title='From Name'
19
+ )
20
+
21
+
22
+ class AlertPayload(BaseModel):
23
+ title: str | None = Field(None, description='Notification title', title='Title')
24
+ body: str | None = Field(None, description='Notification body', title='Body')
25
+ subtitle: str | None = Field(
26
+ None, description='Notification subtitle', title='Subtitle'
27
+ )
28
+
29
+
30
+ class ApiKey(BaseModel):
31
+ key_id: str = Field(..., description='API key ID', title='Key Id')
32
+ key: str | None = Field(
33
+ None, description='API key (only shown on creation)', title='Key'
34
+ )
35
+ name: str = Field(..., description='Key name', title='Name')
36
+ created_at: str = Field(..., description='Creation timestamp', title='Created At')
37
+
38
+
39
+ class BulkPushRequest(BaseModel):
40
+ user_ids: list[str] = Field(..., description='Target user IDs', title='User Ids')
41
+ alert: AlertPayload | None = Field(None, description='Alert payload')
42
+ badge: int | None = Field(None, description='Badge count', title='Badge')
43
+ sound: str | None = Field(None, description='Sound name', title='Sound')
44
+ content_available: bool | None = Field(
45
+ False, description='Silent/background push', title='Content Available'
46
+ )
47
+ data: dict[str, Any] | None = Field(
48
+ None, description='Custom data payload', title='Data'
49
+ )
50
+
51
+
52
+ class Channel(Enum):
53
+ push = 'push'
54
+ email = 'email'
55
+ all = 'all'
56
+
57
+
58
+ class ChannelStats(BaseModel):
59
+ sent: int = Field(..., title='Sent')
60
+ delivered: int = Field(..., title='Delivered')
61
+ failed: int = Field(..., title='Failed')
62
+ delivery_rate: float = Field(..., title='Delivery Rate')
63
+
64
+
65
+ class CreateApiKeyRequest(BaseModel):
66
+ name: str = Field(..., description='Name for the API key', title='Name')
67
+
68
+
69
+ class CreateNotifyConfigRequest(BaseModel):
70
+ org_id: str = Field(
71
+ ..., description='Organization ID that owns this app', title='Org Id'
72
+ )
73
+ bundle_id: str | None = Field(
74
+ None, description='iOS app bundle ID', title='Bundle Id'
75
+ )
76
+ team_id: str | None = Field(
77
+ None, description='Apple Developer Team ID', title='Team Id'
78
+ )
79
+ key_id: str | None = Field(None, description='APNs Key ID', title='Key Id')
80
+ private_key: str | None = Field(
81
+ None, description='APNs private key (.p8 contents)', title='Private Key'
82
+ )
83
+ use_sandbox: bool | None = Field(
84
+ True, description='Use APNs sandbox environment', title='Use Sandbox'
85
+ )
86
+
87
+
88
+ class DailyStats(BaseModel):
89
+ date: str = Field(..., title='Date')
90
+ push_sent: int = Field(..., title='Push Sent')
91
+ push_delivered: int = Field(..., title='Push Delivered')
92
+ push_failed: int = Field(..., title='Push Failed')
93
+ email_sent: int = Field(..., title='Email Sent')
94
+ email_delivered: int = Field(..., title='Email Delivered')
95
+ email_failed: int = Field(..., title='Email Failed')
96
+
97
+
98
+ class DnsRecordPurpose(Enum):
99
+ dkim = 'dkim'
100
+
101
+
102
+ class EmailContact(BaseModel):
103
+ email: str = Field(..., description='Email address', title='Email')
104
+ user_id: str = Field(..., description='Associated user ID', title='User Id')
105
+ subscribed: bool | None = Field(
106
+ True, description='Whether contact is subscribed', title='Subscribed'
107
+ )
108
+ created_at: str = Field(
109
+ ..., description='Registration timestamp', title='Created At'
110
+ )
111
+
112
+
113
+ class EmailDomainStatus(Enum):
114
+ pending = 'pending'
115
+ verified = 'verified'
116
+ failed = 'failed'
117
+
118
+
119
+ class HealthResponse(BaseModel):
120
+ status: str | None = Field('ok', description='Service status', title='Status')
121
+ service: str | None = Field(
122
+ 'mushu-notify', description='Service name', title='Service'
123
+ )
124
+ version: str = Field(..., description='Service version', title='Version')
125
+
126
+
127
+ class NotifyConfig(BaseModel):
128
+ app_id: str = Field(..., description='App identifier', title='App Id')
129
+ org_id: str = Field(
130
+ ..., description='Organization ID that owns this app', title='Org Id'
131
+ )
132
+ created_at: str = Field(..., description='Creation timestamp', title='Created At')
133
+ created_by: str = Field(
134
+ ..., description='User ID who created this config', title='Created By'
135
+ )
136
+ bundle_id: str | None = Field(
137
+ None, description='iOS app bundle ID', title='Bundle Id'
138
+ )
139
+ team_id: str | None = Field(
140
+ None, description='Apple Developer Team ID', title='Team Id'
141
+ )
142
+ key_id: str | None = Field(None, description='APNs Key ID', title='Key Id')
143
+ use_sandbox: bool | None = Field(
144
+ True, description='Using APNs sandbox', title='Use Sandbox'
145
+ )
146
+ email_domain: str | None = Field(
147
+ None, description='Verified email domain', title='Email Domain'
148
+ )
149
+ email_domain_status: str | None = Field(
150
+ None, description='Domain verification status', title='Email Domain Status'
151
+ )
152
+ email_from_name: str | None = Field(
153
+ None, description='Default sender name', title='Email From Name'
154
+ )
155
+ email_verified_at: str | None = Field(
156
+ None, description='When email domain was verified', title='Email Verified At'
157
+ )
158
+ push_enabled: bool | None = Field(
159
+ False,
160
+ description='Whether push notifications are configured',
161
+ title='Push Enabled',
162
+ )
163
+ email_enabled: bool | None = Field(
164
+ False,
165
+ description='Whether email is configured and verified',
166
+ title='Email Enabled',
167
+ )
168
+
169
+
170
+ class NotifyConfigListResponse(BaseModel):
171
+ configs: list[NotifyConfig] = Field(..., title='Configs')
172
+
173
+
174
+ class Platform(Enum):
175
+ ios = 'ios'
176
+ watchos = 'watchos'
177
+
178
+
179
+ class PushResult(BaseModel):
180
+ success: bool = Field(..., description='Whether push was sent', title='Success')
181
+ message_id: str | None = Field(
182
+ None, description='APNs message ID', title='Message Id'
183
+ )
184
+ error: str | None = Field(
185
+ None, description='Error message if failed', title='Error'
186
+ )
187
+
188
+
189
+ class RegisterContactRequest(BaseModel):
190
+ user_id: str = Field(
191
+ ..., description='User ID to associate email with', title='User Id'
192
+ )
193
+ email: EmailStr = Field(..., description='Email address', title='Email')
194
+
195
+
196
+ class RegisterDeviceRequest(BaseModel):
197
+ user_id: str = Field(
198
+ ..., description='User ID to associate device with', title='User Id'
199
+ )
200
+ token: str = Field(..., description='APNs device token', title='Token')
201
+ platform: Platform = Field(..., description='Device platform')
202
+ app_version: str | None = Field(
203
+ None, description='App version', title='App Version'
204
+ )
205
+
206
+
207
+ class SendNotifyRequest(BaseModel):
208
+ user_id: str = Field(..., description='Target user ID', title='User Id')
209
+ channel: Channel | None = Field('push', description='Notification channel')
210
+ alert: AlertPayload | None = Field(None, description='Alert payload for push')
211
+ badge: int | None = Field(None, description='Badge count for push', title='Badge')
212
+ sound: str | None = Field(None, description='Sound name for push', title='Sound')
213
+ content_available: bool | None = Field(
214
+ False, description='Silent/background push', title='Content Available'
215
+ )
216
+ data: dict[str, Any] | None = Field(
217
+ None, description='Custom data payload for push', title='Data'
218
+ )
219
+ email_subject: str | None = Field(
220
+ None, description='Email subject', title='Email Subject'
221
+ )
222
+ email_body_html: str | None = Field(
223
+ None, description='Email HTML body', title='Email Body Html'
224
+ )
225
+ email_body_text: str | None = Field(
226
+ None, description='Email plain text body', title='Email Body Text'
227
+ )
228
+ email_from: str | None = Field(
229
+ None, description='Sender email (must be @verified-domain)', title='Email From'
230
+ )
231
+
232
+
233
+ class SendPushRequest(BaseModel):
234
+ user_id: str = Field(..., description='Target user ID', title='User Id')
235
+ alert: AlertPayload | None = Field(None, description='Alert payload')
236
+ badge: int | None = Field(None, description='Badge count', title='Badge')
237
+ sound: str | None = Field(None, description='Sound name', title='Sound')
238
+ content_available: bool | None = Field(
239
+ False, description='Silent/background push', title='Content Available'
240
+ )
241
+ data: dict[str, Any] | None = Field(
242
+ None, description='Custom data payload', title='Data'
243
+ )
244
+
245
+
246
+ class TimeSeriesResponse(BaseModel):
247
+ app_id: str = Field(..., title='App Id')
248
+ start_date: str = Field(..., title='Start Date')
249
+ end_date: str = Field(..., title='End Date')
250
+ daily: list[DailyStats] = Field(..., title='Daily')
251
+
252
+
253
+ class ValidationError(BaseModel):
254
+ loc: list[str | int] = Field(..., title='Location')
255
+ msg: str = Field(..., title='Message')
256
+ type: str = Field(..., title='Error Type')
257
+
258
+
259
+ class VerifyDomainResponse(BaseModel):
260
+ domain: str = Field(..., description='Domain name', title='Domain')
261
+ status: EmailDomainStatus = Field(..., description='Current verification status')
262
+ verified_at: str | None = Field(
263
+ None, description='When verified, if applicable', title='Verified At'
264
+ )
265
+
266
+
267
+ class AnalyticsResponse(BaseModel):
268
+ app_id: str = Field(..., title='App Id')
269
+ start_date: str = Field(..., title='Start Date')
270
+ end_date: str = Field(..., title='End Date')
271
+ total_events: int = Field(..., title='Total Events')
272
+ push: ChannelStats
273
+ email: ChannelStats
274
+
275
+
276
+ class BulkPushResult(BaseModel):
277
+ total: int = Field(..., description='Total users targeted', title='Total')
278
+ success: int = Field(..., description='Successful sends', title='Success')
279
+ failed: int = Field(..., description='Failed sends', title='Failed')
280
+ results: list[PushResult] = Field(
281
+ ..., description='Per-user results', title='Results'
282
+ )
283
+
284
+
285
+ class ContactListResponse(BaseModel):
286
+ contacts: list[EmailContact] = Field(..., title='Contacts')
287
+
288
+
289
+ class Device(BaseModel):
290
+ token: str = Field(..., description='APNs device token', title='Token')
291
+ user_id: str = Field(..., description='Associated user ID', title='User Id')
292
+ platform: Platform = Field(..., description='Device platform')
293
+ app_version: str | None = Field(
294
+ None, description='App version', title='App Version'
295
+ )
296
+ created_at: str = Field(
297
+ ..., description='Registration timestamp', title='Created At'
298
+ )
299
+ updated_at: str = Field(
300
+ ..., description='Last update timestamp', title='Updated At'
301
+ )
302
+
303
+
304
+ class DeviceListResponse(BaseModel):
305
+ devices: list[Device] = Field(..., title='Devices')
306
+
307
+
308
+ class DnsRecord(BaseModel):
309
+ type: str = Field(..., description="Record type (e.g., 'CNAME')", title='Type')
310
+ name: str = Field(..., description='Record name/host', title='Name')
311
+ value: str = Field(..., description='Record value', title='Value')
312
+ purpose: DnsRecordPurpose = Field(..., description='Purpose of this record')
313
+
314
+
315
+ class EmailDomain(BaseModel):
316
+ domain: str = Field(..., description='Domain name', title='Domain')
317
+ status: EmailDomainStatus = Field(..., description='Verification status')
318
+ from_name: str | None = Field(
319
+ None, description='Default sender name', title='From Name'
320
+ )
321
+ dns_records: list[DnsRecord] | None = Field(
322
+ None, description='DNS records to add', title='Dns Records'
323
+ )
324
+ created_at: str = Field(
325
+ ..., description='When domain was added', title='Created At'
326
+ )
327
+ verified_at: str | None = Field(
328
+ None, description='When domain was verified', title='Verified At'
329
+ )
330
+
331
+
332
+ class HTTPValidationError(BaseModel):
333
+ detail: list[ValidationError] | None = Field(None, title='Detail')
@@ -0,0 +1,345 @@
1
+ # generated by datamodel-codegen:
2
+ # filename: pay-openapi.json
3
+ # timestamp: 2026-01-25T18:54:24+00:00
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+
9
+ from pydantic import BaseModel, Field, PositiveInt, conint
10
+
11
+
12
+ class ApiKeyCreatedResponse(BaseModel):
13
+ key_id: str = Field(..., description='API key ID', title='Key Id')
14
+ name: str = Field(..., description='API key name', title='Name')
15
+ api_key: str = Field(
16
+ ..., description='The API key (only shown once)', title='Api Key'
17
+ )
18
+ created_at: str = Field(..., description='Creation timestamp', title='Created At')
19
+
20
+
21
+ class ApiKeyResponse(BaseModel):
22
+ key_id: str = Field(..., description='API key ID', title='Key Id')
23
+ name: str = Field(..., description='API key name', title='Name')
24
+ created_at: str = Field(..., description='Creation timestamp', title='Created At')
25
+
26
+
27
+ class AutoRefillConfig(BaseModel):
28
+ enabled: bool = Field(
29
+ ..., description='Whether auto-refill is enabled', title='Enabled'
30
+ )
31
+ product_id: str | None = Field(
32
+ None, description='Product to purchase for auto-refill', title='Product Id'
33
+ )
34
+ threshold: conint(ge=0) | None = Field(
35
+ 0,
36
+ description='Trigger auto-refill when balance falls below this (in micro-dollars)',
37
+ title='Threshold',
38
+ )
39
+
40
+
41
+ class BillingModel(Enum):
42
+ one_time = 'one_time'
43
+ subscription = 'subscription'
44
+ usage_based = 'usage_based'
45
+ tiered = 'tiered'
46
+
47
+
48
+ class CheckoutRequest(BaseModel):
49
+ product_id: str = Field(..., description='Product to purchase', title='Product Id')
50
+ success_url: str = Field(
51
+ ..., description='URL to redirect on success', title='Success Url'
52
+ )
53
+ cancel_url: str = Field(
54
+ ..., description='URL to redirect on cancel', title='Cancel Url'
55
+ )
56
+
57
+
58
+ class CheckoutResponse(BaseModel):
59
+ checkout_url: str = Field(
60
+ ..., description='Stripe Checkout URL', title='Checkout Url'
61
+ )
62
+
63
+
64
+ class CreateApiKeyRequest(BaseModel):
65
+ name: str = Field(..., description='Name for this API key', title='Name')
66
+
67
+
68
+ class CreateOrgBillingRequest(BaseModel):
69
+ name: str = Field(..., description='Human-readable name for billing', title='Name')
70
+ stripe_secret_key: str | None = Field(
71
+ None, description='Stripe secret key', title='Stripe Secret Key'
72
+ )
73
+ stripe_webhook_secret: str | None = Field(
74
+ None, description='Stripe webhook signing secret', title='Stripe Webhook Secret'
75
+ )
76
+ free_tier_amount: int | None = Field(
77
+ 0,
78
+ description='Amount granted to new organizations in micro-dollars (0 to disable)',
79
+ title='Free Tier Amount',
80
+ )
81
+
82
+
83
+ class HealthResponse(BaseModel):
84
+ status: str = Field(..., description='Health status', title='Status')
85
+ service: str = Field(..., description='Service name', title='Service')
86
+ version: str = Field(..., description='Service version', title='Version')
87
+
88
+
89
+ class OrgBilling(BaseModel):
90
+ org_id: str = Field(..., description='Organization ID', title='Org Id')
91
+ name: str = Field(..., description='Billing config name', title='Name')
92
+ is_platform: bool | None = Field(
93
+ False, description="True if this is Mushu's own billing", title='Is Platform'
94
+ )
95
+ has_stripe_key: bool = Field(
96
+ ..., description='Whether Stripe key is configured', title='Has Stripe Key'
97
+ )
98
+ has_webhook_secret: bool = Field(
99
+ ...,
100
+ description='Whether webhook secret is configured',
101
+ title='Has Webhook Secret',
102
+ )
103
+ free_tier_amount: int | None = Field(
104
+ 0,
105
+ description='Amount granted to new orgs in micro-dollars',
106
+ title='Free Tier Amount',
107
+ )
108
+ wallet_balance: int | None = Field(
109
+ 0, description='Current wallet balance in micro-dollars', title='Wallet Balance'
110
+ )
111
+ wallet_balance_usd: str | None = Field(
112
+ '$0.00',
113
+ description='Wallet balance formatted as USD',
114
+ title='Wallet Balance Usd',
115
+ )
116
+ auto_refill_enabled: bool | None = Field(
117
+ False, description='Whether auto-refill is enabled', title='Auto Refill Enabled'
118
+ )
119
+ auto_refill_product_id: str | None = Field(
120
+ None, description='Product for auto-refill', title='Auto Refill Product Id'
121
+ )
122
+ auto_refill_threshold: int | None = Field(
123
+ 0,
124
+ description='Auto-refill threshold in micro-dollars',
125
+ title='Auto Refill Threshold',
126
+ )
127
+ has_payment_method: bool | None = Field(
128
+ False,
129
+ description='Whether a payment method is saved',
130
+ title='Has Payment Method',
131
+ )
132
+ created_at: str = Field(
133
+ ..., description='ISO timestamp of creation', title='Created At'
134
+ )
135
+ created_by: str = Field(
136
+ ..., description='User ID who created this', title='Created By'
137
+ )
138
+
139
+
140
+ class PriceTier(BaseModel):
141
+ up_to: int | None = Field(
142
+ None, description='Maximum units for this tier (None = infinity)', title='Up To'
143
+ )
144
+ price_per_unit_cents: int = Field(
145
+ ..., description='Price per unit in cents', title='Price Per Unit Cents'
146
+ )
147
+ flat_fee_cents: int | None = Field(
148
+ None, description='Optional flat fee for this tier', title='Flat Fee Cents'
149
+ )
150
+
151
+
152
+ class SetupIntentResponse(BaseModel):
153
+ client_secret: str = Field(
154
+ ..., description='Stripe client secret for Stripe.js', title='Client Secret'
155
+ )
156
+ setup_intent_id: str = Field(
157
+ ..., description='Setup intent ID', title='Setup Intent Id'
158
+ )
159
+
160
+
161
+ class UpdateOrgBillingRequest(BaseModel):
162
+ name: str | None = Field(None, description='Updated name', title='Name')
163
+ stripe_secret_key: str | None = Field(
164
+ None, description='Updated Stripe secret key', title='Stripe Secret Key'
165
+ )
166
+ stripe_webhook_secret: str | None = Field(
167
+ None, description='Updated Stripe webhook secret', title='Stripe Webhook Secret'
168
+ )
169
+ free_tier_amount: int | None = Field(
170
+ None,
171
+ description='Amount granted to new organizations in micro-dollars (0 to disable)',
172
+ title='Free Tier Amount',
173
+ )
174
+
175
+
176
+ class UpdateProductRequest(BaseModel):
177
+ name: str | None = Field(None, description='Updated name', title='Name')
178
+ price_cents: int | None = Field(
179
+ None, description='Updated price', title='Price Cents'
180
+ )
181
+ active: bool | None = Field(
182
+ None, description='Whether product is active', title='Active'
183
+ )
184
+ amount_micros: int | None = Field(
185
+ None,
186
+ description='Updated amount in micro-dollars deposited to wallet',
187
+ title='Amount Micros',
188
+ )
189
+
190
+
191
+ class ValidationError(BaseModel):
192
+ loc: list[str | int] = Field(..., title='Location')
193
+ msg: str = Field(..., title='Message')
194
+ type: str = Field(..., title='Error Type')
195
+
196
+
197
+ class WalletOperationType(Enum):
198
+ deposit = 'deposit'
199
+ charge = 'charge'
200
+
201
+
202
+ class WalletTransaction(BaseModel):
203
+ org_id: str = Field(..., description='Organization ID', title='Org Id')
204
+ transaction_id: str = Field(
205
+ ..., description='Transaction ID', title='Transaction Id'
206
+ )
207
+ type: str = Field(
208
+ ..., description='Transaction type (deposit, charge, refund)', title='Type'
209
+ )
210
+ amount_micros: int = Field(
211
+ ...,
212
+ description='Amount in micro-dollars (positive=deposit, negative=charge)',
213
+ title='Amount Micros',
214
+ )
215
+ amount_usd: str = Field(
216
+ ..., description='Amount formatted as USD', title='Amount Usd'
217
+ )
218
+ balance_after: int = Field(
219
+ ...,
220
+ description='Balance after transaction in micro-dollars',
221
+ title='Balance After',
222
+ )
223
+ balance_after_usd: str = Field(
224
+ ...,
225
+ description='Balance after transaction formatted as USD',
226
+ title='Balance After Usd',
227
+ )
228
+ description: str = Field(
229
+ ..., description='Transaction description', title='Description'
230
+ )
231
+ stripe_payment_id: str | None = Field(
232
+ None, description='Stripe payment ID', title='Stripe Payment Id'
233
+ )
234
+ app_id: str | None = Field(
235
+ None, description='App that triggered this', title='App Id'
236
+ )
237
+ created_at: str = Field(..., description='ISO timestamp', title='Created At')
238
+
239
+
240
+ class ApiKeyListResponse(BaseModel):
241
+ api_keys: list[ApiKeyResponse] = Field(
242
+ ..., description='List of API keys', title='Api Keys'
243
+ )
244
+
245
+
246
+ class CreateProductRequest(BaseModel):
247
+ name: str = Field(..., description='Product name', title='Name')
248
+ billing_model: BillingModel = Field(..., description='Billing model')
249
+ price_cents: int = Field(
250
+ ..., description='Base price in cents', title='Price Cents'
251
+ )
252
+ amount_micros: int | None = Field(
253
+ None,
254
+ description='Amount in micro-dollars deposited to wallet on purchase (for one_time)',
255
+ title='Amount Micros',
256
+ )
257
+ interval: str | None = Field(
258
+ None,
259
+ description="Billing interval: 'month' or 'year' (for subscription)",
260
+ title='Interval',
261
+ )
262
+ interval_amount_micros: int | None = Field(
263
+ None,
264
+ description='Amount in micro-dollars deposited per billing period (for subscription)',
265
+ title='Interval Amount Micros',
266
+ )
267
+ unit_name: str | None = Field(
268
+ None,
269
+ description="Name of the unit, e.g., 'API call' (for usage_based)",
270
+ title='Unit Name',
271
+ )
272
+ price_per_unit_cents: int | None = Field(
273
+ None,
274
+ description='Price per unit in cents (for usage_based)',
275
+ title='Price Per Unit Cents',
276
+ )
277
+ tiers: list[PriceTier] | None = Field(
278
+ None, description='Volume pricing tiers (for tiered)', title='Tiers'
279
+ )
280
+
281
+
282
+ class HTTPValidationError(BaseModel):
283
+ detail: list[ValidationError] | None = Field(None, title='Detail')
284
+
285
+
286
+ class PayProduct(BaseModel):
287
+ org_id: str = Field(..., description='Owning organization ID', title='Org Id')
288
+ product_id: str = Field(
289
+ ..., description='Unique product identifier', title='Product Id'
290
+ )
291
+ name: str = Field(..., description='Product name', title='Name')
292
+ billing_model: BillingModel = Field(..., description='Billing model')
293
+ price_cents: int = Field(
294
+ ..., description='Base price in cents', title='Price Cents'
295
+ )
296
+ stripe_price_id: str = Field(
297
+ ..., description='Stripe Price ID', title='Stripe Price Id'
298
+ )
299
+ active: bool = Field(..., description='Whether product is active', title='Active')
300
+ created_at: str = Field(
301
+ ..., description='ISO timestamp of creation', title='Created At'
302
+ )
303
+ amount_micros: int | None = Field(
304
+ None,
305
+ description='Amount in micro-dollars deposited to wallet on purchase',
306
+ title='Amount Micros',
307
+ )
308
+ interval: str | None = Field(None, description='Billing interval', title='Interval')
309
+ interval_amount_micros: int | None = Field(
310
+ None,
311
+ description='Amount in micro-dollars deposited per billing period',
312
+ title='Interval Amount Micros',
313
+ )
314
+ unit_name: str | None = Field(None, description='Unit name', title='Unit Name')
315
+ price_per_unit_cents: int | None = Field(
316
+ None, description='Price per unit', title='Price Per Unit Cents'
317
+ )
318
+ tiers: list[PriceTier] | None = Field(
319
+ None, description='Pricing tiers', title='Tiers'
320
+ )
321
+
322
+
323
+ class ProductListResponse(BaseModel):
324
+ products: list[PayProduct] = Field(
325
+ ..., description='List of products', title='Products'
326
+ )
327
+
328
+
329
+ class TransactionListResponse(BaseModel):
330
+ transactions: list[WalletTransaction] = Field(
331
+ ..., description='List of transactions', title='Transactions'
332
+ )
333
+
334
+
335
+ class WalletOperationRequest(BaseModel):
336
+ operation: WalletOperationType = Field(..., description='Operation type')
337
+ amount_micros: PositiveInt = Field(
338
+ ..., description='Amount in micro-dollars', title='Amount Micros'
339
+ )
340
+ description: str = Field(
341
+ ..., description='Reason for the operation', title='Description'
342
+ )
343
+ app_id: str | None = Field(
344
+ None, description='App that triggered this (for charges)', title='App Id'
345
+ )
@@ -0,0 +1,23 @@
1
+ [project]
2
+ name = "mushu-sdk"
3
+ version = "0.1.2"
4
+ description = "Python types for Mushu API - auto-generated from OpenAPI specs"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ license = {text = "MIT"}
8
+ keywords = ["mushu", "api", "sdk", "openapi"]
9
+ dependencies = [
10
+ "pydantic[email]>=2.0.0",
11
+ ]
12
+
13
+ [project.urls]
14
+ Homepage = "https://admin.mushucorp.com"
15
+ Documentation = "https://admin.mushucorp.com/docs"
16
+ Repository = "https://github.com/mushucorp/mushu"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
21
+
22
+ [tool.hatch.build.targets.wheel]
23
+ packages = ["mushu"]