Skip to main content

An easy to use Python wrapper for the Pterodactyl Panel API.

Project description

pydactyl

lint-and-test Latest docs Coverage Latest version Discord

An easy to use Python wrapper for the Pterodactyl Panel API.

State of the project

Support for the Pterodactyl 1.x API is complete. The 2.0 Pydactyl release was created to standardize how includes and params are passed. As a result some endpoints (namely nests) will break when upgrading from Pydactyl 1.x to 2.0.

Pull Requests are being accepted and new endpoints will continue to be added.

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://pydactyl.readthedocs.io/ .

Installing

To install with pip:

pip install py-dactyl

If you get an error saying ImportError: cannot import name 'PterodactylClient' from 'pydactyl' this probably means you installed the wrong package from pip.

Getting Started

Pterodactyl 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

pydactyl 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 pydactyl import PterodactylClient

api = PterodactylClient('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 Pterodactyl 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 pydactyl import PterodactylClient

# Create a client to connect to the panel and authenticate with your API key.
api = PterodactylClient('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

As of Pterodactyl 1.8.0 Application API keys are deprecated and client API keys should now be used exclusively.

The Application API is the administrative API of the Pterodactyl 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 pydactyl import PterodactylClient

# Create a client to connect to the panel and authenticate with your API key.
api = PterodactylClient('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 Pydactyl 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'])
pydactyl.exceptions.PterodactylApiError: 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

Pterodactyl 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.

Pydactyl 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 Pydactyl, 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})

PterodactylClient

Each of the classes in pydactyl could be imported independently, but the PterodactylClient class pydactyl/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 PterodactylClient 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.

PterodactylClient('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.

PterodactylClient('foo', 'bar', extra_retry_codes=[502, 504])

Debug logging

Most errors from pydactyl 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 PterodactylClient.

app_api = PterodactylClient('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

Pydactyl supports asynchronous usage via AsyncPterodactylClient. 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 pydactyl import AsyncPterodactylClient


async def main():
    # Create an async client
    async with AsyncPterodactylClient('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 AsyncPterodactylClient mirror the structure of PterodactylClient but are awaitable.

Websocket Client

Pydactyl provides a helper class to interact with the Wings websocket for real-time console interaction and stats. This helper is available for both synchronous and asynchronous usage.

Authentication

Websocket auth tokens are good for 10 minutes. There are 2 token expiring event 30 seconds apart before a final token expired event is sent.

{'event': 'token expiring'}
{'event': 'token expiring'}
{'event': 'token expired'}
{'event': 'jwt error', 'args': ['jwt: exp claim is invalid']}

The websocket clients handle the token refresh automatically.

$ python3 async_websocket_example.py
2025-12-14 23:43:32.421537 - {'event': 'auth success'}
2025-12-14 23:43:32.421648 - {'event': 'status', 'args': ['running']}
2025-12-14 23:52:32.560222 - {'event': 'token expiring'}
2025-12-14 23:52:32.583737 - {'event': 'auth success'}

Synchronous Usage

from pydactyl import PterodactylClient

api = PterodactylClient('https://panel.mydomain.com', 'MyApiKey')

# Authenticate and connect to the websocket
# The context manager automatically handles connection and cleanup
with api.client.servers.get_websocket_client('server_uuid') as ws:
    # Send a command
    ws.send_command('say Hello World')

    # Listen for console output messages
    for msg in ws.listen(event='console output'):
        print(msg)

Asynchronous Usage

import asyncio
from pydactyl import AsyncPterodactylClient


async def main():
    async with AsyncPterodactylClient('https://panel.mydomain.com', 'MyApiKey') as api:
        # Authenticate and connect
        # get_websocket_client is awaitable and returns the client instance
        ws_client = await api.client.servers.get_websocket_client('server_uuid')
        async with ws_client as ws:
            await ws.send_command('say Hello Async')

            async for msg in ws.listen():
                print(msg)


if __name__ == '__main__':
    asyncio.run(main())

Example scripts

Example scripts for using the websocket clients can be found at: async_websocket_example.py websocket_example.py

Paginated Responses

Pydactyl 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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py_dactyl-2.1.2.tar.gz (56.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py_dactyl-2.1.2-py3-none-any.whl (94.4 kB view details)

Uploaded Python 3

File details

Details for the file py_dactyl-2.1.2.tar.gz.

File metadata

  • Download URL: py_dactyl-2.1.2.tar.gz
  • Upload date:
  • Size: 56.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for py_dactyl-2.1.2.tar.gz
Algorithm Hash digest
SHA256 21ff2d1f3661ee24f45c3e59352adc6461fb78ef084be884e438b32f617f8d9a
MD5 fc881851598a0d682369181596a2e8db
BLAKE2b-256 2de49f49b953dd775d84b16917bdec42c3c540926df6f4c34daaab3f3444a865

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_dactyl-2.1.2.tar.gz:

Publisher: test-build-and-publish-on-release.yml on iamkubi/pydactyl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py_dactyl-2.1.2-py3-none-any.whl.

File metadata

  • Download URL: py_dactyl-2.1.2-py3-none-any.whl
  • Upload date:
  • Size: 94.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for py_dactyl-2.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ce1c349e4240e278353e8ef3688d15b2fd06d147349b5745d939213bae93daf7
MD5 34138840ffa39992b1d7561cb8790ee6
BLAKE2b-256 0fcd3f4e84b5833599c73226f2dcaa3a8adba943c8d7b0ba0484bbbc0e2a53bd

See more details on using hashes here.

Provenance

The following attestation bundles were made for py_dactyl-2.1.2-py3-none-any.whl:

Publisher: test-build-and-publish-on-release.yml on iamkubi/pydactyl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page