pyluga
An easy to use Python wrapper for the Beluga Panel API.
State of the project
Support for the Beluga 1.x API is complete.
Pull Requests are accepted and the project will be updated as the API changes.
If you encounter problems, find APIs that haven't been implemented, or have a feature request please file a Github issue.
Documentation
Generated documentation can be found at https://pyluga.readthedocs.io/ .
Installing
To install with pip:
pip install pyluga
If you get an error saying ImportError: cannot import name 'BelugaClient' from 'pyluga' this probably means you installed the
wrong package from pip.
Getting Started
Beluga has two different types of API keys: Client (also known as Account) and Application. Any user can generate an Account API key to control their own servers. The Account API key for an Administrator user will be able to access any server's Client API.
Application API keys can only be generated by administrators. These keys can be used to create, modify, and delete servers, among other things. They have access to any server on the panel and can be destructive, so use with care.
Sync vs Async
pyluga has both synchronous and asynchronous clients. Which one you use depends on your use case. The synchronous client blocks until the request is complete, while the asynchronous client returns a coroutine that can be run in an event loop.
The asynchronous client is recommended for multi-user environments as it does not block the main thread.
Examples in this README primarily use the synchronous client. For examples on using the async client see Async Client.
Exploring the API
In addition to the documentation you can explore the interface in an
interactive Python interpreter using built-ins like dir() and the
__doc__ attribute as shown below.
from pyluga import BelugaClient
api = BelugaClient('debug', 'anything')
[i for i in dir(api.client.servers) if not i.startswith('_')]
# ['account', 'backups', 'databases', 'files', 'get_server', 'get_server_utilization', 'list_permissions', 'list_servers', 'network', 'schedules', 'send_console_command', 'send_power_action', 'servers', 'settings', 'startup', 'users']
[i for i in dir(api.client.servers.settings) if not i.startswith('_')]
# ['reinstall_server', 'rename_server']
print(api.client.servers.settings.rename_server.__doc__)
#Renames the server.
# Args:
# server_id(str): Server identifier (abbreviated UUID)
# name(str): New name for the server
Client API
The Client API or Account API is accessed by users of the Beluga panel. Below is the layout of the Client API namespace.
api.client.account
api.client.servers
api.client.servers.backups
api.client.servers.databases
api.client.servers.files
api.client.servers.network
api.client.servers.schedules
api.client.servers.settings
api.client.servers.startup
api.client.servers.users
A full list of methods available can be found in the documentation.
Below are examples of how you might get information about your servers.
from pyluga import BelugaClient
# Create a client to connect to the panel and authenticate with your API key.
api = BelugaClient('https://panel.mydomain.com', 'MySuperSecretApiKey')
# Get a list of all servers the user has access to
my_servers = api.client.servers.list_servers()
# Get the unique identifier for the first server.
srv_id = my_servers[0]['attributes']['identifier']
# Check the utilization of the server
srv_utilization = api.client.servers.get_server_utilization(srv_id)
print(srv_utilization)
# Turn the server on.
api.client.servers.send_power_action(srv_id, 'start')
Application API
The Application API is the administrative API of the Beluga panel. Below is the layout of the Application API namespace.
api.locations
api.nests
api.nodes
api.servers
api.user
Below are examples of how you might use this API.
from pyluga import BelugaClient
# Create a client to connect to the panel and authenticate with your API key.
api = BelugaClient('https://panel.mydomain.com', 'MySuperSecretApiKey')
# Create a server. Customize the Nest and Egg IDs to match the IDs in your panel.
# This server is created with a limit of 8000 MB of memory, no access to swap, unlimited disk space, in location_id 1.
api.servers.create_server(name='My Paper Server', user_id=1, nest_id=1,
egg_id=3, memory_limit=8000, swap_limit=0,
backup_limit=0, disk_limit=0, location_ids=[1])
< Response[201] >
A 201 response indicates success, however if there is a problem with the request Pyluga will raise an exception with additional details. When updating the location_ids field to an invalid location it displays an error:
api.servers.create_server(name='My Paper Server', user_id=1, nest_id=1,
egg_id=3, memory_limit=8000, swap_limit=0,
disk_limit=0, location_ids=[199])
Traceback (most recent call last):
File "<input>", line 6, in <module>
File "D:\code\pydactyl\pydactyl\api\servers.py", line 268, in create_server
mode='POST', data=data, json=False)
File "D:\code\pydactyl\pydactyl\api\base.py", line 98, in _api_request
'code'], errors['detail'])
pyluga.exceptions.BelugaApiError: Bad API Request(400) - NoViableNodeException - No nodes satisfying the requirements specified for automatic deployment could be found.
You can use the User class to add, modify, and delete panel users.
# Create a new user
result = api.user.create_user('test_user', 'test@gmail.com', 'Test', 'Name')
# Get the ID of the created user
user_id = result['attributes']['id']
# Get the user info, also returned by create_user()
api.user.get_user_info(user_id)
{'object': 'user', 'attributes': {'id': 14, 'external_id': None, .... }}
# Delete the user
api.user.delete_user(user_id=14)
Includes
Beluga API endpoints have different sets of includes you can pass to alter the response. Using includes will cause additional information to show up in the relationships field of the response data. Some endpoints have no includes and some have many.
Pyluga docstrings include examples of valid includes for each endpoint, but they are not an exhaustive list.
server_includes = [
'allocations', 'user', 'subusers', 'pack', 'nest', 'egg', 'variables',
'location', 'node', 'databases']
As an example the application server details endpoint has 10 potential includes according to the API docs. Note that the API docs are not always accurate either!
Most endpoints that generate lists will have optional includes that can be passed as lists or tuples.
api.nodes.list_nodes(includes=('allocations', 'location'))
api.servers.get_server_info(
server_id=53,
includes=['user', 'subusers', 'location'])
Params
Most endpoints with includes will also have a params parameter. This can
be used to pass additional parameters. Many endpoint specific params are
already supported by Pyluga, however some additional params apply
universally like per_page.
api.nodes.list_nodes(params={'per_page': 9001})
api.servers.list_servers(params={'per_page': 9001})
api.users.list_users(params={'per_page': 9001})
BelugaClient
Each of the classes in pyluga could be imported independently, but the BelugaClient class pyluga/api_client.py provides a simplified interface that imports libraries for you and provides some convenience features like retries and debug logging.
Retries
Instances of BelugaClient will automatically retry calls that fail with a 429 status code indicating that the request was rate-limited by Pterodactyl.
By default it uses a backoff_factor of 1 with 3 retries. You can configure the number of retries and backoff_factor when instantiating a client.
BelugaClient('panel', 'key', backoff_factor=2, retries=10)
Details on backoff_factor and retires can be found in the urllib3.util.Retry documentation .
If your server is overloaded or intermittently unavailable you may want to retry on other status codes as well. To do this you can pass in a list of integer HTTP status codes.
BelugaClient('foo', 'bar', extra_retry_codes=[502, 504])
Debug logging
Most errors from pyluga will present as exceptions and there is no logging
by default, however sometimes additional logging is helpful. You can get
request logging by passing debug=True to BelugaClient.
app_api = BelugaClient('https://why', 'broken', debug=True)
app_api.servers.list_servers(includes=('egg', 'nest'))
DEBUG:urllib3.connectionpool:https://why:443 "GET /api/application/servers?include=egg%2Cnest HTTP/1.1" 200 None
Async Client
Pyluga supports asynchronous usage via AsyncBelugaClient. This client uses aiohttp and asyncio to perform non-blocking API calls.
Usage
The async client is designed to be used as a context manager:
import asyncio
from pyluga import AsyncBelugaClient
async def main():
# Create an async client
async with AsyncBelugaClient('https://panel.mydomain.com', 'MyApiKey') as api:
# Get a list of servers
servers = await api.client.servers.list_servers()
# Async iteration over paginated results
async for page in servers:
for server in page:
print(server['attributes']['name'])
# Or collect all results asynchronously
all_servers = await servers.collect_async()
if __name__ == '__main__':
asyncio.run(main())
All methods in AsyncBelugaClient mirror the structure of BelugaClient but are awaitable.
Paginated Responses
Pyluga API responses return a PaginatedResponse object that can be iterated over to automatically fetch additional pages as required. This is currently implemented on many endpoints which frequently return multi-page responses, but not all.
# Create a list of all ports
allocs = api.nodes.list_node_allocations(node_id)
ports = []
for page in allocs:
for item in page:
ports.append(item['attributes']['port'])
len(ports)
151
collect()
The collect() method will fetch the data from all pages of a
PaginatedResponse. This allows you to easily fetch all results when you want all
the data without having to iterate over the pages.
The above example to get a list of ports now looks like:
# Create a list of all ports
allocs = api.nodes.list_node_allocations(node_id)
ports = allocs.collect()
len(ports)
151
Metadata
Release files for pyluga 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyluga-2.1.0.tar.gz | 55.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyluga-2.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 142.6 kB
Release files / pyluga-2.1.0.tar.gz
| Download URL | pyluga-2.1.0.tar.gz |
|---|---|
| Size | 55.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
08e6388f888ebf75a90679cfe17994010f0dc023b09380942d842c6903dbc266
|
|
BLAKE2b-256 checksum How to use checksums |
67ac686eef192de9877d58835eb9b7e0ce6e39e4d1b92038b065ef0ca3996300
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Dec 14, 2025.
Transparency logRelease files / pyluga-2.1.0-py3-none-any.whl
| Download URL | pyluga-2.1.0-py3-none-any.whl |
|---|---|
| Size | 87.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
123305faa10163a310f46b6c178dec48271a0eeb290f9462cc48b439ba0af496
|
|
BLAKE2b-256 checksum How to use checksums |
38237d6073040027c4d8ef6d9b51788930c17f423e814b89b1989e1825f4025e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Dec 14, 2025.
Transparency log