Async Python client for the DesslyHub API:
Steam, mobile games, vouchers, eSIM, merchant balance, orders and currency exchange rates.
English · Русский
Built on aiohttp + orjson, responses are validated with pydantic, and monetary values are represented as Decimal. All endpoints live under the base URL https://desslyhub.com with the /api/v1/ prefix.
- Features
- Installation
- Quick start
- Authentication
- Usage
- Orders
- Money values and error handling
- Development
- License
| Resource | Purpose |
|---|---|
client.steam |
Catalog of gift games, game editions and login check for top-ups. |
client.mobile |
Catalog of mobile games and details of a specific game. |
client.vouchers |
Catalog of voucher products and a product by id. |
client.esim |
Catalog of eSIM variants with cursor pagination and variant details. |
client.merchant |
Current merchant balance. |
client.exchange |
Currency rates against USD for Steam top-ups. |
client.orders |
Single entry point for creating all purchases (top-ups, gifts, vouchers, eSIM), plus listing and order details. |
All purchases are created through the
ordersresource (single endpointPOST /api/v1/orders). The oldgift/top_up/refill/buymethods and themerchantsresource with/transactionsendpoints were removed — order history and statuses are now handled by orders.
Requirements: Python 3.12+ and uv.
uv add desslyhub-api
# or
pip install desslyhub-apiImport the package: import desslyhub_api.
Clone the repository and install all dependency groups (including the dev tools: pytest, black, isort, mypy):
uv sync --all-groupsimport asyncio
from desslyhub_api import DesslyHubClient
async def main() -> None:
async with DesslyHubClient("YOUR_API_KEY", "YOUR_SECRET") as client:
balance = await client.merchant.get_balance()
print(balance.balance) # Decimal
asyncio.run(main())The client is an async context manager: inside async with it opens a persistent HTTP session that is closed automatically on exit.
Every request is signed with HMAC-SHA256: signature = HMAC_SHA256(secret, api_key + timestamp + body). The client adds the X-Api-Key, X-Timestamp (Unix time in seconds) and X-Signature headers to every request. So you need two values — api_key and secret. The default base URL is https://desslyhub.com.
from desslyhub_api import DesslyHubClient
# Minimal
client = DesslyHubClient("YOUR_API_KEY", "YOUR_SECRET")
# Overriding base_url and timeout
client = DesslyHubClient(
"YOUR_API_KEY",
"YOUR_SECRET",
base_url="https://desslyhub.com",
timeout_seconds=30.0,
)All methods are async and called inside async with DesslyHubClient(api_key, secret) as client:.
balance = await client.merchant.get_balance()
print(balance.balance) # Decimal — total balance
print(balance.available_balance) # Decimal — available to spend
print(balance.overdraft) # Decimal — overdraft limit
print(balance.reserve) # Decimal — reserved fundsget_balance() returns a Balance model with fields balance, available_balance, overdraft, reserve — all of type Decimal.
games = await client.steam.get_games()
for game in games:
print(game)
# Editions of a specific game by its app_id
editions = await client.steam.get_editions(app_id=730)
for edition in editions:
print(edition)
# Whether a Steam account with the given login can be topped up
can_refill = await client.steam.check_login(username="steam_user")
print(can_refill) # boolgames = await client.mobile.get_games()
for game in games:
print(game)
details = await client.mobile.get_game(game_id=42)
print(details)products = await client.vouchers.get_products()
for product in products:
print(product)
product = await client.vouchers.get_product(product_id=10)
print(product)page = await client.esim.get_products() # cursor=None — first page
for variant in page.variants:
print(variant)
# Next page by cursor
if page.next_cursor:
next_page = await client.esim.get_products(cursor=page.next_cursor)
print(next_page.variants)
details = await client.esim.get_product(variant_id="variant-123")
print(details)from desslyhub_api import Currency
all_rates = await client.exchange.get_all_rates()
print(all_rates.rates) # dict[int, float]
# Rate for a specific currency (Currency enum or numeric code)
rate = await client.exchange.get_rate(Currency.RUB)
print(rate.rate)All purchases are created through the client.orders resource. Each create_* method returns CreateOrderResponse with fields order_id (int) and status (str). Typical flow:
- Create an order with a
create_*method — the response containsorder_idandstatus. - Poll the order via
client.orders.get(order_id), trackingorder_statusandservice_result(the result becomes available after successful completion).
All create_* methods share common keyword-only optional parameters:
payment_method: PaymentMethod— payment method, defaults toPaymentMethod.BALANCE(charge the merchant balance). For QR-link payment usePaymentMethod.PAYLINK_QR.payment_params: PaylinkQRPaymentParams | None— QR payment parameters (required forPAYLINK_QR).reference: str | None— your external order id.
# Steam top-up
order = await client.orders.create_steam_refill(
username="steam_user",
amount=10.0, # amount in USD
reference="my-order-id", # optional
)
print(order.order_id, order.status)
# Steam game gift
order = await client.orders.create_steam_gift(
invite_url="https://steamcommunity.com/p/...",
package_id="12345",
region="RU",
)
# Voucher purchase
order = await client.orders.create_voucher(
root_id=10,
variant_id=2,
quantity=1,
)
# Mobile game top-up
order = await client.orders.create_mobile_refill(
position=3,
fields={"player_id": "123456"},
)
# eSIM purchase
order = await client.orders.create_esim(
product_id="product-123",
variant_id="variant-456",
)from desslyhub_api import PaymentMethod, PaylinkQRPaymentParams
order = await client.orders.create_steam_refill(
username="steam_user",
amount=10.0,
payment_method=PaymentMethod.PAYLINK_QR,
payment_params=PaylinkQRPaymentParams(
amount=1000,
success_url="https://example.com/success",
fail_url="https://example.com/fail",
),
reference="my-order-id",
)
print(order.order_id, order.status)# List page with limit/offset pagination
page = await client.orders.list(limit=20, offset=0)
print(page.total, page.limit, page.offset)
for item in page.items:
print(item.order_id, item.order_status)
# Details of a specific order
details = await client.orders.get(order_id=12345)
print(details.order_status) # str — order status
print(details.service_type) # str — service type
print(details.commission) # Decimal — commission
print(details.final_amount) # Decimal — final amount
print(details.service_result) # execution result (after success)list() returns OrderList with fields items, total, limit, offset. get() returns OrderDetails with fields order_id, order_status, service_type, payment_method, commission and final_amount (both Decimal), timestamps and service_result.
- Monetary values (e.g.
Balancefields, as well ascommissionandfinal_amountinOrderDetails) are represented asDecimalto avoid rounding errors. - On a response HTTP status
>= 400the client raisesDesslyHubAPIError. The error body is parsed as RFC 7807 (ErrorModel). The exception exposeserror_code(int),message(str) andhttp_status(int | None) attributes. - Network problems raise
DesslyHubConnectionError. - An invalid/unreadable API response raises
DesslyHubResponseError. - All exceptions inherit from the base
DesslyHubError.
from desslyhub_api import (
DesslyHubAPIError,
DesslyHubConnectionError,
DesslyHubResponseError,
)
async with DesslyHubClient(api_key, secret) as client:
try:
balance = await client.merchant.get_balance()
except DesslyHubAPIError as error:
print("API error:", error.error_code, error.message, error.http_status)
except DesslyHubConnectionError as error:
print("Connection error:", error)
except DesslyHubResponseError as error:
print("Invalid response:", error)Set up the environment with all dev dependencies (pytest, black, isort, mypy):
uv sync --all-groupsUseful commands:
uv run pytest -q # tests
uv run isort . && uv run black . # formatting
uv run mypy desslyhub_api # type checkingMIT.