smsbower 0.1.0__py3-none-any.whl

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.
smsbower/__init__.py ADDED
@@ -0,0 +1,15 @@
1
+ """SMSBower Python Client."""
2
+
3
+ from .client import DEFAULT_REF_ID, SmsBowerClient
4
+ from .errors import SmsBowerError
5
+ from .types import ActivationNumber, ActivationStatus, CountryItem, ServiceItem
6
+
7
+ __all__ = [
8
+ "DEFAULT_REF_ID",
9
+ "ActivationNumber",
10
+ "ActivationStatus",
11
+ "CountryItem",
12
+ "ServiceItem",
13
+ "SmsBowerClient",
14
+ "SmsBowerError",
15
+ ]
smsbower/client.py ADDED
@@ -0,0 +1,483 @@
1
+ """SMSBower API client implementation."""
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ import httpx
7
+
8
+ from .errors import SmsBowerError
9
+ from .types import ActivationNumber, ActivationStatus, CountryItem, ServiceItem
10
+
11
+ DEFAULT_REF_ID = "572438"
12
+ DEFAULT_BASE_URL = "https://smsbower.page"
13
+
14
+
15
+ class SmsBowerClient:
16
+ """Client for communicating with the SMSBower activation API.
17
+
18
+ Args:
19
+ api_key: SMSBower API key.
20
+ base_url: Base URL of the API. Defaults to https://smsbower.page.
21
+ timeout: HTTP request timeout in seconds. Defaults to 30.0.
22
+
23
+ Examples:
24
+ >>> from smsbower import SmsBowerClient
25
+ >>> client = SmsBowerClient(api_key="your_token")
26
+ >>> balance = client.get_balance()
27
+ >>> print(balance)
28
+ """
29
+
30
+ def __init__(
31
+ self,
32
+ api_key: str,
33
+ base_url: str = DEFAULT_BASE_URL,
34
+ timeout: float = 30.0,
35
+ ) -> None:
36
+ if not api_key or not api_key.strip():
37
+ raise SmsBowerError("api_key is required to initialize SmsBowerClient")
38
+ self.api_key = api_key.strip()
39
+ self.base_url = base_url.rstrip("/")
40
+ self._client = httpx.Client(base_url=self.base_url, timeout=timeout)
41
+
42
+ def __enter__(self) -> "SmsBowerClient":
43
+ return self
44
+
45
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
46
+ self.close()
47
+
48
+ def close(self) -> None:
49
+ """Closes the underlying HTTP client session."""
50
+ self._client.close()
51
+
52
+ def get_balance(self) -> float:
53
+ """Retrieves the current account balance.
54
+
55
+ Returns:
56
+ The account balance as a float.
57
+
58
+ Raises:
59
+ SmsBowerError: If the server returns an error or unexpected response.
60
+
61
+ Examples:
62
+ >>> client = SmsBowerClient(api_key="key")
63
+ >>> balance = client.get_balance()
64
+ >>> print(f"Balance: {balance}")
65
+ """
66
+ data = self._send_handler_request({"action": "getBalance"})
67
+ if isinstance(data, str) and data.startswith("ACCESS_BALANCE:"):
68
+ try:
69
+ return float(data.split(":", 1)[1])
70
+ except ValueError as exc:
71
+ raise SmsBowerError(f"Failed to parse balance: {data}", "PARSE_ERROR") from exc
72
+ raise SmsBowerError(f"Unexpected getBalance response: {data}", "INVALID_RESPONSE", data)
73
+
74
+ def get_number(
75
+ self,
76
+ service: str,
77
+ country: str | int | None = None,
78
+ max_price: float | None = None,
79
+ min_price: float | None = None,
80
+ provider_ids: str | list[str | int] | None = None,
81
+ except_provider_ids: str | list[str | int] | None = None,
82
+ phone_exception: str | None = None,
83
+ user_id: str | int | None = None,
84
+ ) -> ActivationNumber:
85
+ """Orders a virtual phone number for SMS verification.
86
+
87
+ Args:
88
+ service: Service code (e.g. 'tg', 'wa').
89
+ country: Country code or ID (e.g. '22').
90
+ max_price: Maximum price willing to pay.
91
+ min_price: Minimum price willing to pay.
92
+ provider_ids: Provider IDs whitelist.
93
+ except_provider_ids: Provider IDs blacklist.
94
+ phone_exception: Phone prefix exception filters.
95
+ user_id: Reseller user identifier.
96
+
97
+ Returns:
98
+ ActivationNumber with ID and phone number.
99
+
100
+ Raises:
101
+ SmsBowerError: If no numbers are available or an API error occurs.
102
+
103
+ Examples:
104
+ >>> client = SmsBowerClient(api_key="key")
105
+ >>> number = client.get_number("tg", country="22")
106
+ >>> print(f"ID: {number.id}, Number: {number.number}")
107
+ """
108
+ params: dict[str, Any] = {
109
+ "action": "getNumber",
110
+ "service": service,
111
+ "ref": DEFAULT_REF_ID,
112
+ }
113
+ if country is not None:
114
+ params["country"] = country
115
+ if max_price is not None:
116
+ params["maxPrice"] = max_price
117
+ if min_price is not None:
118
+ params["minPrice"] = min_price
119
+ if provider_ids is not None:
120
+ params["providerIds"] = (
121
+ ",".join(map(str, provider_ids)) if isinstance(provider_ids, list) else provider_ids
122
+ )
123
+ if except_provider_ids is not None:
124
+ params["exceptProviderIds"] = (
125
+ ",".join(map(str, except_provider_ids))
126
+ if isinstance(except_provider_ids, list)
127
+ else except_provider_ids
128
+ )
129
+ if phone_exception is not None:
130
+ params["phoneException"] = phone_exception
131
+ if user_id is not None:
132
+ params["userID"] = user_id
133
+
134
+ data = self._send_handler_request(params)
135
+ if isinstance(data, str) and data.startswith("ACCESS_NUMBER:"):
136
+ parts = data.split(":")
137
+ if len(parts) >= 3:
138
+ return ActivationNumber(id=parts[1], number=parts[2])
139
+ raise SmsBowerError(f"Unexpected getNumber response: {data}", "INVALID_RESPONSE", data)
140
+
141
+ def get_number_v2(
142
+ self,
143
+ service: str,
144
+ country: str | int | None = None,
145
+ max_price: float | None = None,
146
+ min_price: float | None = None,
147
+ provider_ids: str | list[str | int] | None = None,
148
+ except_provider_ids: str | list[str | int] | None = None,
149
+ phone_exception: str | None = None,
150
+ user_id: str | int | None = None,
151
+ ) -> dict[str, Any]:
152
+ """Orders a virtual phone number with rich metadata (v2 protocol).
153
+
154
+ Args:
155
+ service: Service code.
156
+ country: Country code or ID.
157
+ max_price: Maximum price willing to pay.
158
+ min_price: Minimum price willing to pay.
159
+ provider_ids: Provider IDs whitelist.
160
+ except_provider_ids: Provider IDs blacklist.
161
+ phone_exception: Phone prefix exception filters.
162
+ user_id: Reseller user identifier.
163
+
164
+ Returns:
165
+ Dictionary with full activation details.
166
+
167
+ Raises:
168
+ SmsBowerError: If order fails or returns an API error.
169
+
170
+ Examples:
171
+ >>> client = SmsBowerClient(api_key="key")
172
+ >>> order = client.get_number_v2("tg", country="22")
173
+ >>> print(order["phoneNumber"], order.get("activationCost"))
174
+ """
175
+ params: dict[str, Any] = {
176
+ "action": "getNumberV2",
177
+ "service": service,
178
+ "ref": DEFAULT_REF_ID,
179
+ }
180
+ if country is not None:
181
+ params["country"] = country
182
+ if max_price is not None:
183
+ params["maxPrice"] = max_price
184
+ if min_price is not None:
185
+ params["minPrice"] = min_price
186
+ if provider_ids is not None:
187
+ params["providerIds"] = (
188
+ ",".join(map(str, provider_ids)) if isinstance(provider_ids, list) else provider_ids
189
+ )
190
+ if except_provider_ids is not None:
191
+ params["exceptProviderIds"] = (
192
+ ",".join(map(str, except_provider_ids))
193
+ if isinstance(except_provider_ids, list)
194
+ else except_provider_ids
195
+ )
196
+ if phone_exception is not None:
197
+ params["phoneException"] = phone_exception
198
+ if user_id is not None:
199
+ params["userID"] = user_id
200
+
201
+ data = self._send_handler_request(params)
202
+ if isinstance(data, dict):
203
+ return data
204
+ if isinstance(data, str):
205
+ try:
206
+ parsed = json.loads(data)
207
+ if isinstance(parsed, dict):
208
+ return parsed
209
+ except json.JSONDecodeError:
210
+ pass
211
+ raise SmsBowerError(f"Unexpected getNumberV2 response: {data}", "INVALID_RESPONSE", data)
212
+
213
+ def get_status(self, activation_id: str | int) -> ActivationStatus:
214
+ """Retrieves the status and SMS code of an activation.
215
+
216
+ Args:
217
+ activation_id: Unique activation ID.
218
+
219
+ Returns:
220
+ ActivationStatus object with status and optional code.
221
+
222
+ Examples:
223
+ >>> client = SmsBowerClient(api_key="key")
224
+ >>> status = client.get_status("123456")
225
+ >>> if status.status == "STATUS_OK":
226
+ ... print("Code:", status.code)
227
+ """
228
+ data = self._send_handler_request({"action": "getStatus", "id": activation_id})
229
+ if isinstance(data, str):
230
+ if data.startswith("STATUS_OK:"):
231
+ return ActivationStatus(status="STATUS_OK", code=data.split(":", 1)[1].strip())
232
+ if data.startswith("STATUS_WAIT_RETRY:"):
233
+ return ActivationStatus(
234
+ status="STATUS_WAIT_RETRY", code=data.split(":", 1)[1].strip()
235
+ )
236
+ return ActivationStatus(status=data.strip())
237
+ raise SmsBowerError(f"Unexpected getStatus response: {data}", "INVALID_RESPONSE", data)
238
+
239
+ def set_status(self, activation_id: str | int, status: int) -> str:
240
+ """Changes activation lifecycle status.
241
+
242
+ Status codes:
243
+ 1: SMS sent / number ready
244
+ 3: Request another code
245
+ 6: Complete activation
246
+ 8: Cancel activation
247
+
248
+ Args:
249
+ activation_id: Unique activation ID.
250
+ status: Status code number.
251
+
252
+ Returns:
253
+ Server response code string.
254
+
255
+ Examples:
256
+ >>> client = SmsBowerClient(api_key="key")
257
+ >>> client.set_status("123456", 6)
258
+ 'ACCESS_ACTIVATION'
259
+ """
260
+ data = self._send_handler_request(
261
+ {
262
+ "action": "setStatus",
263
+ "id": activation_id,
264
+ "status": status,
265
+ }
266
+ )
267
+ return str(data).strip()
268
+
269
+ def get_prices(
270
+ self,
271
+ service: str | None = None,
272
+ country: str | int | None = None,
273
+ ) -> dict[str, Any]:
274
+ """Retrieves the standard service price catalog.
275
+
276
+ Args:
277
+ service: Optional service code filter.
278
+ country: Optional country ID filter.
279
+
280
+ Returns:
281
+ Nested dictionary of prices and counts.
282
+
283
+ Examples:
284
+ >>> client = SmsBowerClient(api_key="key")
285
+ >>> prices = client.get_prices("tg", "22")
286
+ >>> print(prices)
287
+ """
288
+ params: dict[str, Any] = {"action": "getPrices"}
289
+ if service:
290
+ params["service"] = service
291
+ if country is not None:
292
+ params["country"] = country
293
+ data = self._send_handler_request(params)
294
+ return data if isinstance(data, dict) else {}
295
+
296
+ def get_prices_v2(
297
+ self,
298
+ service: str | None = None,
299
+ country: str | int | None = None,
300
+ ) -> dict[str, Any]:
301
+ """Retrieves detailed price list grouped by price points.
302
+
303
+ Args:
304
+ service: Optional service code filter.
305
+ country: Optional country ID filter.
306
+
307
+ Returns:
308
+ Price mapping dictionary.
309
+
310
+ Examples:
311
+ >>> client = SmsBowerClient(api_key="key")
312
+ >>> v2_prices = client.get_prices_v2("tg", "22")
313
+ """
314
+ params: dict[str, Any] = {"action": "getPricesV2"}
315
+ if service:
316
+ params["service"] = service
317
+ if country is not None:
318
+ params["country"] = country
319
+ data = self._send_handler_request(params)
320
+ return data if isinstance(data, dict) else {}
321
+
322
+ def get_prices_v3(
323
+ self,
324
+ service: str | None = None,
325
+ country: str | int | None = None,
326
+ ) -> dict[str, Any]:
327
+ """Retrieves granular provider-level price list.
328
+
329
+ Args:
330
+ service: Optional service code filter.
331
+ country: Optional country ID filter.
332
+
333
+ Returns:
334
+ Provider price mapping.
335
+
336
+ Examples:
337
+ >>> client = SmsBowerClient(api_key="key")
338
+ >>> v3_prices = client.get_prices_v3("tg", "22")
339
+ """
340
+ params: dict[str, Any] = {"action": "getPricesV3"}
341
+ if service:
342
+ params["service"] = service
343
+ if country is not None:
344
+ params["country"] = country
345
+ data = self._send_handler_request(params)
346
+ return data if isinstance(data, dict) else {}
347
+
348
+ def get_services_list(self) -> list[ServiceItem]:
349
+ """Retrieves all supported services.
350
+
351
+ Returns:
352
+ List of ServiceItem objects.
353
+
354
+ Examples:
355
+ >>> client = SmsBowerClient(api_key="key")
356
+ >>> services = client.get_services_list()
357
+ >>> print(f"Supported services: {len(services)}")
358
+ """
359
+ data = self._send_handler_request({"action": "getServicesList"})
360
+ if isinstance(data, dict) and "services" in data and isinstance(data["services"], list):
361
+ return [
362
+ ServiceItem(code=item.get("code", ""), name=item.get("name", ""))
363
+ for item in data["services"]
364
+ ]
365
+ if isinstance(data, list):
366
+ return [
367
+ ServiceItem(code=item.get("code", ""), name=item.get("name", "")) for item in data
368
+ ]
369
+ return []
370
+
371
+ def get_countries(self) -> dict[str, CountryItem]:
372
+ """Retrieves all available countries.
373
+
374
+ Returns:
375
+ Dictionary mapping country IDs to CountryItem objects.
376
+
377
+ Examples:
378
+ >>> client = SmsBowerClient(api_key="key")
379
+ >>> countries = client.get_countries()
380
+ >>> print(countries["22"].eng)
381
+ """
382
+ data = self._send_handler_request({"action": "getCountries"})
383
+ countries: dict[str, CountryItem] = {}
384
+ if isinstance(data, dict):
385
+ for cid, val in data.items():
386
+ if isinstance(val, dict):
387
+ countries[str(cid)] = CountryItem(
388
+ id=str(val.get("id", cid)),
389
+ rus=val.get("rus"),
390
+ eng=val.get("eng"),
391
+ chn=val.get("chn"),
392
+ )
393
+ return countries
394
+
395
+ def get_top_countries_by_service(self, service: str) -> dict[str, Any]:
396
+ """Retrieves top 10 countries and gold partners for a service.
397
+
398
+ Args:
399
+ service: Service code.
400
+
401
+ Returns:
402
+ Dictionary of top countries and partners.
403
+
404
+ Examples:
405
+ >>> client = SmsBowerClient(api_key="key")
406
+ >>> top = client.get_top_countries_by_service("tg")
407
+ """
408
+ data = self._send_handler_request(
409
+ {
410
+ "action": "getTopCountriesByService",
411
+ "service": service,
412
+ }
413
+ )
414
+ return data if isinstance(data, dict) else {}
415
+
416
+ def get_actual_wallet_address(self, coin: str, network: str) -> str:
417
+ """Retrieves static crypto deposit address.
418
+
419
+ Args:
420
+ coin: Cryptocurrency symbol (e.g. 'usdt', 'trx').
421
+ network: Blockchain network (e.g. 'tron').
422
+
423
+ Returns:
424
+ Wallet deposit address string.
425
+
426
+ Raises:
427
+ SmsBowerError: If request fails.
428
+
429
+ Examples:
430
+ >>> client = SmsBowerClient(api_key="key")
431
+ >>> addr = client.get_actual_wallet_address("usdt", "tron")
432
+ >>> print("Deposit address:", addr)
433
+ """
434
+ try:
435
+ resp = self._client.get(
436
+ "/api/payment/getActualWalletAddress",
437
+ params={
438
+ "api_key": self.api_key,
439
+ "coin": coin,
440
+ "network": network,
441
+ },
442
+ )
443
+ resp.raise_for_status()
444
+ data = resp.json()
445
+ if isinstance(data, dict) and "wallet_address" in data:
446
+ return str(data["wallet_address"])
447
+ raise SmsBowerError(f"Unexpected wallet response: {resp.text}", "INVALID_RESPONSE")
448
+ except httpx.HTTPError as exc:
449
+ raise SmsBowerError(f"Wallet request failed: {exc}", "NETWORK_ERROR") from exc
450
+
451
+ def _send_handler_request(self, params: dict[str, Any]) -> Any:
452
+ query = {"api_key": self.api_key, **params}
453
+ try:
454
+ resp = self._client.get("/stubs/handler_api.php", params=query)
455
+ resp.raise_for_status()
456
+ text = resp.text.strip()
457
+
458
+ error_keywords = [
459
+ "BAD_KEY",
460
+ "BAD_ACTION",
461
+ "BAD_SERVICE",
462
+ "NO_NUMBERS",
463
+ "NO_BALANCE",
464
+ "NO_ACTIVATION",
465
+ "BAD_STATUS",
466
+ "EARLY_CANCEL_DENIED",
467
+ "BAD_COUNTRY",
468
+ ]
469
+ for kw in error_keywords:
470
+ if text == kw or text.startswith(f"{kw}:"):
471
+ raise SmsBowerError(f"SMSBower API error: {text}", code=kw, response_body=text)
472
+
473
+ if (text.startswith("{") and text.endswith("}")) or (
474
+ text.startswith("[") and text.endswith("]")
475
+ ):
476
+ try:
477
+ return json.loads(text)
478
+ except json.JSONDecodeError:
479
+ pass
480
+
481
+ return text
482
+ except httpx.HTTPError as exc:
483
+ raise SmsBowerError(f"HTTP request failed: {exc}", "NETWORK_ERROR") from exc
smsbower/errors.py ADDED
@@ -0,0 +1,17 @@
1
+ """Exceptions for the SMSBower client."""
2
+
3
+ from typing import Any
4
+
5
+
6
+ class SmsBowerError(Exception):
7
+ """Base exception for all SMSBower client errors."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ code: str | None = None,
13
+ response_body: Any | None = None,
14
+ ) -> None:
15
+ super().__init__(message)
16
+ self.code = code
17
+ self.response_body = response_body
smsbower/types.py ADDED
@@ -0,0 +1,37 @@
1
+ """Type definitions for the SMSBower API."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass
7
+ class ActivationNumber:
8
+ """Represents an acquired virtual phone number."""
9
+
10
+ id: str
11
+ number: str
12
+
13
+
14
+ @dataclass
15
+ class ActivationStatus:
16
+ """Status of an activation."""
17
+
18
+ status: str
19
+ code: str | None = None
20
+
21
+
22
+ @dataclass
23
+ class ServiceItem:
24
+ """Supported SMSBower service item."""
25
+
26
+ code: str
27
+ name: str
28
+
29
+
30
+ @dataclass
31
+ class CountryItem:
32
+ """Country metadata item."""
33
+
34
+ id: str
35
+ rus: str | None = None
36
+ eng: str | None = None
37
+ chn: str | None = None
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.5
2
+ Name: smsbower
3
+ Version: 0.1.0
4
+ Summary: Python client for the SMSBower virtual number and SMS activation API
5
+ Project-URL: Repository, https://github.com/TysikGG/api-kit
6
+ Project-URL: Documentation, https://smsbower.app/api?page=client
7
+ License-Expression: ISC
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.27
11
+ Description-Content-Type: text/markdown
12
+
13
+ # smsbower
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/smsbower.svg)](https://pypi.org/project/smsbower/)
16
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
17
+ [![Python Version](https://img.shields.io/pypi/pyversions/smsbower.svg)](https://pypi.org/project/smsbower/)
18
+
19
+ Modern, fully typed Python client library for the [SMSBower](https://smsbower.com) virtual phone numbers and SMS OTP receiving API.
20
+
21
+ Official API documentation: <https://smsbower.com/api.html>
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ - 📱 **Virtual Phone Numbers** — Disposable phone numbers across 200+ countries for Telegram, WhatsApp, Discord, Google, and more.
28
+ - 🔄 **Full Activation Lifecycle** — Reserve numbers, mark ready, poll for incoming SMS OTP codes, complete, or cancel.
29
+ - 🐍 **Modern Pythonic Design** — Type annotations, dataclass models (`ActivationNumber`, `ActivationStatus`), context manager support (`with SmsBowerClient(...) as client:`), and custom exceptions.
30
+ - 🏷️ **Comprehensive Pricing** — V1, V2, and V3 pricing queries, service catalogs, top country recommendations, and availability stats.
31
+
32
+ ---
33
+
34
+ ## Installation
35
+
36
+ ```bash
37
+ pip install smsbower
38
+ ```
39
+
40
+ Requires Python 3.10 or higher.
41
+
42
+ ---
43
+
44
+ ## Quick Start (Safe Check)
45
+
46
+ The quick check only inspects your account balance and supported countries. It does not order numbers or consume funds:
47
+
48
+ ```python
49
+ import os
50
+ from smsbower import SmsBowerClient
51
+
52
+ with SmsBowerClient(api_key=os.environ["SMSBOWER_API_KEY"]) as client:
53
+ balance = client.get_balance()
54
+ print(f"Current SMSBower balance: {balance:.2f} RUB")
55
+
56
+ countries = client.get_countries()
57
+ print(f"Supported countries count: {len(countries)}")
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Usage Guide
63
+
64
+ ### 1. Renting a Number and Receiving an SMS Code
65
+
66
+ Full activation lifecycle: order a number, notify readiness, poll for the incoming verification code, and complete the order:
67
+
68
+ ```python
69
+ import time
70
+ from smsbower import SmsBowerClient
71
+
72
+ with SmsBowerClient(api_key="your_api_key") as client:
73
+ # 1. Order a number for Telegram in USA (country '187')
74
+ order = client.get_number(service="tg", country="187")
75
+ print(f"Rented Number: +{order.number} (Activation ID: {order.activation_id})")
76
+
77
+ # 2. Notify provider that you are ready to receive the SMS (status 1)
78
+ client.set_status(order.activation_id, 1)
79
+
80
+ # 3. Poll for the incoming verification code
81
+ code = None
82
+ for _ in range(30):
83
+ time.sleep(3)
84
+ res = client.get_status(order.activation_id)
85
+ if res.status == "STATUS_OK" and res.code:
86
+ code = res.code
87
+ break
88
+
89
+ if code:
90
+ print(f"Received verification code: {code}")
91
+ # 4. Mark activation completed (status 6)
92
+ client.set_status(order.activation_id, 6)
93
+ else:
94
+ print("SMS did not arrive. Cancelling activation to refund balance (status 8)...")
95
+ client.set_status(order.activation_id, 8)
96
+ ```
97
+
98
+ ### 2. Searching Prices and Stock Availability
99
+
100
+ Query real-time prices and availability across services and countries:
101
+
102
+ ```python
103
+ # Query prices across countries for Telegram
104
+ prices = client.get_prices("tg")
105
+ print("Prices for Telegram:", prices)
106
+
107
+ # Inspect top recommended countries for Telegram
108
+ top = client.get_top_countries_by_service("tg")
109
+ print("Top countries:", top)
110
+ ```
111
+
112
+ ---
113
+
114
+ ## API Reference
115
+
116
+ ### `SmsBowerClient` Class
117
+
118
+ #### Constructor
119
+
120
+ ```python
121
+ SmsBowerClient(
122
+ api_key: str,
123
+ base_url: str = "https://smsbower.com",
124
+ timeout: float = 30.0,
125
+ )
126
+ ```
127
+
128
+ | Parameter | Type | Default | Description |
129
+ | :--- | :--- | :--- | :--- |
130
+ | `api_key` | `str` | **Required** | Your SMSBower API key. |
131
+ | `base_url` | `str` | `"https://smsbower.com"` | Base URL of SMSBower API. |
132
+ | `timeout` | `float` | `30.0` | Request timeout in seconds. |
133
+
134
+ #### Methods
135
+
136
+ - **`get_balance() -> float`** — Retrieves account balance in RUB.
137
+ - **`get_number(service, country=None, ...) -> ActivationNumber`** — Orders a number (`activation_id`, `number`).
138
+ - **`get_number_v2(service, country=None, ...) -> ActivationNumberV2`** — Orders number using extended V2 format.
139
+ - **`get_status(activation_id: str | int) -> ActivationStatus`** — Checks activation state and code (`STATUS_WAIT_CODE`, `STATUS_OK`).
140
+ - **`set_status(activation_id: str | int, status: int) -> str`** — Sets activation state (1 = ready, 6 = complete, 8 = cancel).
141
+ - **`get_prices(service=None, country=None) -> Any`** — Returns V1 prices and availability.
142
+ - **`get_prices_v2(service=None, country=None) -> Any`** — Returns V2 prices.
143
+ - **`get_prices_v3(service=None, country=None) -> Any`** — Returns V3 prices.
144
+ - **`get_services_list() -> list[ServiceItem]`** — Lists all supported services.
145
+ - **`get_countries() -> dict[str, CountryItem]`** — Lists all supported countries.
146
+ - **`get_top_countries_by_service(service: str) -> dict[str, Any]`** — Recommended countries for service.
147
+ - **`get_actual_wallet_address(coin: str, network: str) -> str`** — Returns deposit wallet address for top-up.
148
+
149
+ ---
150
+
151
+ ## Error Handling
152
+
153
+ When the API returns an error status code, `SmsBowerError` is raised:
154
+
155
+ ```python
156
+ from smsbower import SmsBowerClient, SmsBowerError
157
+
158
+ try:
159
+ with SmsBowerClient(api_key="invalid_key") as client:
160
+ client.get_balance()
161
+ except SmsBowerError as exc:
162
+ print(f"SMSBower Error: {exc.message} (Code: {exc.code})")
163
+ ```
164
+
165
+ Common error codes:
166
+ - `NO_NUMBERS` — No available numbers for selected service/country.
167
+ - `NO_BALANCE` — Insufficient funds on account.
168
+ - `BAD_KEY` — Invalid API key.
169
+ - `BAD_ACTION` — Invalid action requested.
170
+ - `WRONG_ACTIVATION_ID` — Activation ID does not exist or expired.
171
+
172
+ ---
173
+
174
+ ## License
175
+
176
+ ISC
@@ -0,0 +1,8 @@
1
+ smsbower/__init__.py,sha256=3kJX38lExCpZDiiu1KQkbqKnVhV-k46Bavz4GqoknKg,362
2
+ smsbower/client.py,sha256=z-WT9AGyUKX6Y9C0lQ00lQNB4BvkYajH1oT9vQa5rlQ,17075
3
+ smsbower/errors.py,sha256=PNr-ynVGR1r0d-NxYi4gHtoVqxYzaT1an0s8SjP-4A0,404
4
+ smsbower/types.py,sha256=KTjtZt6WWjShM_tN5qjF_jIN5VQhpV6DzKyJ13YPvAM,581
5
+ smsbower-0.1.0.dist-info/METADATA,sha256=DLxgEix2ALJ7hbLrcxZZcDyzkL8dNazjayZX04pht3c,6028
6
+ smsbower-0.1.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
7
+ smsbower-0.1.0.dist-info/licenses/LICENSE,sha256=8S0uYmn_aeljwFudfBoV91HcIo79yab9s0yhMVsOHJ0,751
8
+ smsbower-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 api-kit contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.