Aether Panel Documentation

API Introduction

Aether Panel (SkyPanel) API is a complete and powerful RESTful API that enables automation and programmatic management of all aspects of the panel. It is designed following industry standards and supports OAuth2 for secure authentication.

With this API you can completely automate the management of servers, users, nodes, databases, backups and much more. Ideal for integrations with billing systems, custom control panels, Discord bots, or any application that needs to interact with Aether Panel.

API Base URL

All API requests must be made to the following base URL:

http://tu-servidor:8080/api

https://panel.tudominio.com/api

In development, use IP or localhost. In production, always use HTTPS for security.

Versioning

The API is versioned to ensure compatibility. The current version is v1. All responses include the `X-API-Version` header with the API version used.

Authentication

API requests are authenticated using Bearer Tokens. You can generate API keys from your account settings in the panel. All API requests must be made over HTTPS.

API Features
  • RESTful: Follows standard REST principles
  • OAuth2: Secure authentication with tokens
  • JSON: JSON data format for requests and responses
  • Versioned: Versioned API for compatibility
  • Documented: Complete documentation with examples
  • WebSocket: Real-time communication support
  • Pagination: Paginated results for large datasets
  • Filtering: Advanced search and filtering
Authentication Example
https://dominio:8080/api
OAuth2 Authentication

SkyPanel uses OAuth2 with the **Client Credentials** flow for API authentication.

Step 1: Create an OAuth2 Client

You can create an OAuth2 client from:

  • Web Panel: Settings → OAuth2 Clients
  • CLI: `skypanel oauth2 create`
Step 2: Get Access Token

Endpoint

POST /oauth2/token

Headers

http Content-Type: application/x-www-form-urlencoded

Body

grant_type=client_credentials client_id=TU_CLIENT_ID client_secret=TU_CLIENT_SECRET

cURL Example

curl -X POST http://localhost:8080/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET"

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "server.view server.edit"
}
Step 3: Use the Token

Include the token in the `Authorization` header of all requests:

http Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Example

curl -X GET http://localhost:8080/api/servers   -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Scopes (Permissions)

Scopes define the permissions a client has to access certain endpoints. For example, a client with the `server.read` scope can read server information, but cannot create or modify them.

ScopeDescription
adminFull administrative access
server.viewView servers
server.createCreate servers
server.editEdit servers
server.deleteDelete servers
server.startStart servers
server.stopStop servers
server.consoleConsole access
server.files.viewView files
server.files.editEdit files
users.viewView users
users.editEdit users
nodes.viewView nodes
nodes.editEdit nodes
Data Format
Content-Type

All requests and responses use JSON:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "server.view server.edit"
}
Successful Response Structure
{
  "data": { ... },
                "metadata": {
                    "paging": {
                    "page": 1,
                "size": 25,
                "maxSize": 100,
                "total": 150
    }
  }
}
HTTP Status Codes
CodeMeaningDescription
200OKSuccessful request
201CreatedResource created successfully
204No ContentSuccessful request with no response content
400Bad RequestInvalid input data
401UnauthorizedInvalid or expired token
403ForbiddenNo permission for this action
404Not FoundResource not found
500Internal Server ErrorServer error
Error Handling
Error Structure
{
  "error": {
    "code": "ErrServerNotFound",
    "msg": "Server with ID {id} not found",
    "metadata": {
      "id": "ABC12345"
    }
  }
}
Common Error Codes
CodeDescription
ErrFieldRequiredMissing required field
ErrFieldInvalidInvalid field value
ErrServerNotFoundServer not found
ErrUserNotFoundUser not found
ErrNodeNotFoundNode not found
ErrPermissionDeniedPermission denied
ErrDatabaseErrorDatabase error

Pagination

Endpoints that return lists support pagination:

Query Parameters
ParameterTypeDefaultDescription
pageint1Page number (1-indexed)
limitint25Items per page

Maximum: 100 items per page

Example
GET /api/servers?page=2&limit=50
Response with Metadata
{
  "servers": [...],
  "metadata": {
    "paging": {
      "page": 2,
      "size": 50,
      "maxSize": 100,
      "total": 237
    }
  }
}

Server Endpoints

List Servers

Endpoint: GET /api/servers

Scopes: server.view

Query Parameters
ParámetroTipoDescripción
usernamestringFilter by user (admin only)
nodeuintFilter by node ID
namestringFilter by name (supports * as wildcard)
pageuintPage number
limituintItems per page
Example
curl -X GET "http://localhost:8080/api/servers?username=admin*" \
  -H "Authorization: Bearer YOUR_TOKEN"
Respuesta
{
  "servers": [
    {
      "id": 1,
      "uuid": "abc12345...",
      "name": "Survival Server",
      "ownerId": 1,
      "nodeId": 1,
      "image": "ghcr.io/pterodactyl/yolks:java_17",
      "startup": "java -Xms128M -Xmx1024M -jar server.jar"
    }
  ],
  "metadata": {
    "paging": {
      "page": 1,
      "size": 25,
      "maxSize": 100,
      "total": 1
    }
  }
}
Get Server

Endpoint: GET /api/servers/:serverId

Scopes: server.view

Parámetros
ParámetroTipoUbicaciónDescripción
serverIdstringPathServer ID
permsbooleanQueryInclude user permissions
Respuesta
{
  "id": 1,
  "uuid": "abc12345...",
  "name": "Survival Server",
  "ownerId": 1,
  "nodeId": 1,
  "status": "installing",
  "resources": {
    "memory": 1024,
    "disk": 5120,
    "cpu": 100
  },
  "deployment": {
    "port": 25565,
    "ip": "192.168.1.1"
  }
}
Create Server

Endpoint: PUT /api/servers/:serverId

Scopes: server.create

Body
{
  "name": "New Server",
  "ownerId": 1,
  "eggId": 1,
  "dockerImage": "ghcr.io/pterodactyl/yolks:java_17",
  "startup": "java -Xms128M -Xmx{{SERVER_MEMORY}}M -jar {{SERVER_JARFILE}}",
  "environment": {
    "SERVER_JARFILE": "server.jar",
    "f": "v"
  },
  "limits": {
    "memory": 1024,
    "swap": 0,
    "disk": 5120,
    "io": 500,
    "cpu": 100
  },
  "featureLimits": {
    "databases": 1,
    "backups": 1
  }
}
Respuesta
{
  "id": 2,
  "uuid": "def67890...",
  "name": "New Server",
  "status": "installing"
}
Update Server Definition

Endpoint: PUT /api/servers/:serverId/definition

Scopes: server.definition.edit

{
  "name": "Nuevo Nombre",
  "type": {
    "type": "minecraft-java"
  },
  "icon": "nuevo-icono.png",
  "server": {
    "run": {
      "command": "java -Xmx4G -jar server.jar nogui"
    },
    "data": {
      "memory": 4096
    }
  },
  "variables": {
    "port": 25566
  }
}

Respuesta: 204 No Content

Delete Server

Endpoint: DELETE /api/servers/:serverId

Scopes: server.delete

ParámetroTipoDescripción
skipNodebooleanDo not delete from node, only from database

Respuesta: 204 No Content

Start Server

Endpoint: POST /api/servers/:serverId/start

Scopes: server.start

Respuesta: 204 No Content

Stop Server

Endpoint: POST /api/servers/:serverId/stop

Scopes: server.stop

Respuesta: 204 No Content

Restart Server

Endpoint: POST /api/servers/:serverId/restart

Scopes: server.start, server.stop

Respuesta: 204 No Content

Kill Server (Force Stop)

Endpoint: POST /api/servers/:serverId/kill

Scopes: server.kill

Respuesta: 204 No Content

Install Server

Endpoint: POST /api/servers/:serverId/install

Scopes: server.install

Respuesta: 204 No Content

Get Server Status

Endpoint: GET /api/servers/:serverId/status

Scopes: server.status

{
  "running": true
}
Get Server Stats

Endpoint: GET /api/servers/:serverId/stats

Scopes: server.stats

{
  "cpu": 45.2,
  "memory": 1536000000,
  "memoryTotal": 2147483648
}
Get Console

Endpoint: GET /api/servers/:serverId/console

Scopes: server.console

Parámetros
ParámetroTipoDescripción
timeintTimestamp to get logs from
Respuesta
{
  "logs": [
    "[10:30:15] [Server thread/INFO]: Starting minecraft server version 1.20.1",
    "[10:30:16] [Server thread/INFO]: Loading properties"
  ]
}
Send Command to Console

Endpoint: POST /api/servers/:serverId/console

Scopes: server.sendCommand

{
  "command": "say Hello World!"
}

Respuesta: 204 No Content

File Management

List Files

Endpoint: GET /api/servers/:serverId/file/*filename

Scopes: server.files.view

Example
curl -X GET "http://localhost:8080/api/servers/ABC12345/file/" \
  -H "Authorization: Bearer YOUR_TOKEN"
Respuesta
{
  "files": [
    {
      "name": "server.jar",
      "size": 45678901,
      "modified": "2024-01-15T10:30:00Z",
      "isFile": true
    },
    {
      "name": "world",
      "size": 0,
      "modified": "2024-01-15T10:25:00Z",
      "isFile": false
    }
  ]
}
Download File

Endpoint: GET /api/servers/:serverId/file/*filename

Scopes: server.files.view

curl -X GET "http://localhost:8080/api/servers/ABC12345/file/server.properties" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -o server.properties
Upload File

Endpoint: PUT /api/servers/:serverId/file/*filename

Scopes: server.files.edit

curl -X PUT "http://localhost:8080/api/servers/ABC12345/file/config.yml" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/octet-stream" \
  --data-binary @config.yml
Delete File

Endpoint: DELETE /api/servers/:serverId/file/*filename

Scopes: server.files.edit

curl -X DELETE "http://localhost:8080/api/servers/ABC12345/file/old-backup.zip" \
  -H "Authorization: Bearer YOUR_TOKEN"

Backups

List Backups

Endpoint: GET /api/servers/:serverId/backup

Scopes: server.backup.view

{
  "backups": [
    {
      "id": "backup-20240115-103000",
      "size": 123456789,
      "created": "2024-01-15T10:30:00Z"
    }
  ]
}
Create Backup

Endpoint: POST /api/servers/:serverId/backup/create

Scopes: server.backup.create

{
  "id": "backup-20240115-120000"
}
Restore Backup

Endpoint: POST /api/servers/:serverId/backup/restore/:backupId

Scopes: server.backup.restore

Respuesta: 204 No Content

Delete Backup

Endpoint: DELETE /api/servers/:serverId/backup/:backupId

Scopes: server.backup.delete

Respuesta: 204 No Content

Download Backup

Endpoint: GET /api/servers/:serverId/backup/download/:backupId

Scopes: server.backup.view

curl -X GET "http://localhost:8080/api/servers/ABC12345/backup/download/backup-20240115-103000" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -o backup.tar.gz

Users

List Users

Endpoint: GET /api/users

Scopes: users.info.search

Example
curl -X GET "http://localhost:8080/api/users?username=admin*" \
  -H "Authorization: Bearer YOUR_TOKEN"
Create User

Endpoint: POST /api/users

Scopes: users.info.edit

{
  "username": "newuser",
  "email": "newuser@example.com",
  "password": "SecurePassword123!"
}
Get User

Endpoint: GET /api/users/:id

Scopes: users.info.view

Update User

Endpoint: POST /api/users/:id

Scopes: users.info.edit

Delete User

Endpoint: DELETE /api/users/:id

Scopes: users.info.edit

Respuesta: 204 No Content

Get User Permissions

Endpoint: GET /api/users/:id/perms

Scopes: users.perms.view

[
  "server.view",
  "server.console"
]
Update User Permissions

Endpoint: PUT /api/users/:id/perms

Scopes: users.perms.edit

{
  "scopes": [
    "server.view",
    "server.console",
    "server.files.view",
    "server.files.edit"
  ]
}

Respuesta: 204 No Content

Nodes

List Nodes

Endpoint: GET /api/nodes

Scopes: nodes.view

[
  {
    "id": 1,
    "name": "Node-01",
    "publicHost": "node1.example.com"
  }
]
Create Node

Endpoint: POST /api/nodes

Scopes: nodes.create

Body
{
  "name": "Node-02",
  "publicHost": "node2.example.com",
  "privateHost": "192.168.1.11",
  "publicPort": 8080,
  "privatePort": 8080,
  "sftpPort": 5657
}
Respuesta
{
  "id": 2,
  "name": "Node-02",
  "publicHost": "node2.example.com",
  "privateHost": "192.168.1.11",
  "publicPort": 8080,
  "privatePort": 8080,
  "sftpPort": 5657,
  "secret": "abc123def456..."
}
Get Node

Endpoint: GET /api/nodes/:id

Scopes: nodes.view

Respuesta
{
  "id": 1,
  "name": "Node-01",
  "publicHost": "node1.example.com",
  "privateHost": "192.168.1.10",
  "publicPort": 8080,
  "privatePort": 8080,
  "sftpPort": 5657
}
Update Node

Endpoint: PUT /api/nodes/:id

Scopes: nodes.edit

Body
{
  "name": "Node-01-Updated",
  "publicHost": "node1-new.example.com"
}

Respuesta: 204 No Content

Delete Node

Endpoint: DELETE /api/nodes/:id

Scopes: nodes.delete

Respuesta: 204 No Content

Get Node System Info

Endpoint: GET /api/nodes/:id/system

Scopes: nodes.view

Respuesta
{
  "cpu": {
    "model": "Intel(R) Xeon(R) CPU E5-2680 v4",
    "cores": 8,
    "threads": 16,
    "mhz": 2400.0
  },
  "memory": {
    "total": 17179869184,
    "used": 8589934592,
    "free": 8589934592
  },
  "disk": {
    "total": 1099511627776,
    "used": 549755813888,
    "free": 549755813888
  },
  "os": {
    "platform": "linux",
    "family": "debian",
    "version": "22.04"
  }
}
Get Node Features

Endpoint: GET /api/nodes/:id/features

Scopes: nodes.view

Respuesta
{
  "docker": true,
  "environments": [
    "standard",
    "docker"
  ]
}
Get Deployment Data

Endpoint: GET /api/nodes/:id/deployment

Scopes: nodes.deploy

Respuesta
{
  "clientId": ".node_1",
  "clientSecret": "abc123def456..."
}

Configuration

Get Configuration

Endpoint: GET /api/settings

Scopes: settings.view

{
  "companyName": "SkyPanel",
  "defaultTheme": "SkyPanel",
  "masterUrl": "https://panel.example.com",
  "registrationEnabled": false
}
Update Configuration

Endpoint: PUT /api/settings

Scopes: settings.edit

Body
{
  "companyName": "Mi Empresa",
  "registrationEnabled": true
}

Respuesta: 204 No Content

Templates

List Templates

Endpoint: GET /api/templates

Scopes: templates.view

Respuesta
{
  "templates": [
    {
      "name": "minecraft-java",
      "display": "Minecraft Java Edition",
      "type": "java",
      "supportedVersions": ["1.20.1", "1.19.4", "1.18.2"]
    },
    {
      "name": "terraria",
      "display": "Terraria",
      "type": "native",
      "supportedVersions": ["1.4.4.9"]
    }
  ]
}
Get Template

Endpoint: GET /api/templates/:name

Scopes: templates.view

Respuesta
{
  "name": "minecraft-java",
  "display": "Minecraft Java Edition",
  "type": "java",
  "install": [
    {
      "type": "mojangdl",
      "version": "{{version}}"
    }
  ],
  "run": {
    "command": "java -Xmx{{memory}}M -jar server.jar nogui",
    "stop": "stop"
  },
  "variables": {
    "version": {
      "type": "string",
      "default": "1.20.1",
      "required": true
    },
    "memory": {
      "type": "integer",
      "default": 2048,
      "required": true
    }
  }
}

WebSocket API

Connect to Real-time Console

Endpoint: WS /api/servers/:serverId/socket

Protocol: WebSocket

Scopes: server.view

Ejemplo con JavaScript
const ws = new WebSocket(`ws://localhost:8080/api/servers/${serverId}/socket`);
ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'auth', token: 'YOUR_TOKEN' }));
};
ws.onmessage = (event) => {
  console.log('Message:', JSON.parse(event.data));
};
WebSocket Message Types
  • Authentication: { "type": "auth", "token": "..." }
  • Console (Server -> Client): { "type": "console", "data": "..." }
  • Command (Client -> Server): { "type": "console", "data": "command" }
  • Stats (Server -> Client): { "type": "stats", "data": { ... } }
  • Status (Server -> Client): { "type": "status", "data": { ... } }

Server Databases

These endpoints allow managing MySQL/MariaDB databases associated with specific servers.

Listar Bases de Datos

Endpoint: GET /api/servers/:serverId/databases

Scopes: server.view

Gets all databases created for a specific server.

Crear Base de Datos

Endpoint: POST /api/servers/:serverId/databases

Scopes: server.edit

Creates a new MySQL/MariaDB database for the server. The panel automatically generates the database name, user and password.

Body
  • databaseHostId: Database Host ID to use (required)
  • databaseName: Custom database name (optional, automatically generated if omitted)
Eliminar Base de Datos

Endpoint: DELETE /api/servers/:serverId/databases/:databaseId

Scopes: server.edit

Deletes a database and its associated user from the MySQL/MariaDB server.

Database Hosts

Database Hosts are MySQL/MariaDB server configurations that the panel can use to automatically create databases.

Listar Database Hosts

Endpoint: GET /api/databasehosts

Scopes: admin

Gets all Database Hosts configured in the panel.

Crear Database Host

Endpoint: POST /api/databasehosts

Scopes: admin

Creates a new Database Host. Requires MySQL credentials with GRANT ALL PRIVILEGES permissions.

Body
  • name: Host name (required)
  • host: MySQL server IP or domain (required)
  • port: MySQL port (default 3306)
  • username: MySQL user with permissions (required)
  • password: User password (required)
  • maxDatabases: Maximum number of databases (optional, unlimited if omitted)
  • nodeId: Linked node ID (optional)

Rate Limiting

The API implements rate limits to prevent abuse and ensure optimal performance for all users.

Current Limits
  • Autenticado: 1000 requests per hour per OAuth2 client
  • No autenticado: 100 requests per hour per IP

Limits are applied per OAuth2 client or per IP for unauthenticated requests.

Best Practices

Follow these recommendations to use the API efficiently and securely:

  • Use HTTPS in production: Never send tokens or credentials over unencrypted HTTP.
  • Store tokens securely: Do not hardcode tokens in your code. Use environment variables or secret management systems.
  • Handle errors appropriately: Always check HTTP status codes and handle errors gracefully.
  • Implement retries with exponential backoff: For requests that fail temporarily, implement retry logic.
  • Use pagination for large lists: Do not try to get all results at once. Use pagination to improve performance.
  • Cache responses when appropriate: Some data does not change frequently and can be cached.
  • Respect rate limits: Implement throttling in your application to not exceed limits.
  • Validate data before sending: Verify that data meets requirements before making requests.
  • Use WebSockets for real-time data: For consoles and statistics, use WebSockets instead of constant polling.
  • Keep your tokens secure: Rotate your client secrets regularly and never share them publicly.

Troubleshooting

Common Issues

401 Unauthorized Error

Your token has expired or is invalid. Get a new token using the /oauth2/token endpoint.

403 Forbidden Error

Your OAuth2 client does not have the necessary scopes for this action. Check the scopes assigned to your client.

404 Not Found Error

The resource you are trying to access does not exist. Verify that the ID is correct and that the resource has not been deleted.

400 Bad Request Error

The data sent is not valid. Review the body structure and required data types.

429 Too Many Requests Error

You have exceeded the rate limit. Wait until the counter resets or implement throttling in your application.

Usage Examples

Practical Examples

Below you will find complete examples of how to use the API in different programming languages. These examples cover the most common use cases.

Common Use Cases

Server Automation

Create scripts to automatically start/stop servers based on schedules or events.

Billing System Integration

Connect Aether Panel with systems like WHMCS, Blesta, or custom solutions to automatically create servers when a client makes a payment.

Monitoring and Alerts

Create custom dashboards or integrate with monitoring systems like Grafana, Prometheus, etc.

Automatic Backups

Schedule automatic backups of your servers using cron jobs or scheduling systems.

Discord Bots

Create Discord bots that allow users to manage their servers from Discord using commands.

cURL Example

curl -X GET "https://aetherpanel.es/api/application/servers" \
-H "Authorization: Bearer <YOUR_API_KEY>" \
-H "Accept: Application/vnd.aether.v1+json"

Python

Instalación
pip install requests
Full Example
import requests
import json

class SkyPanelAPI:
    def __init__(self, base_url, client_id, client_secret):
        self.base_url = base_url
        self.client_id = client_id
        self.client_secret = client_secret
        self.token = None
    
    def authenticate(self):
        """Obtener token de acceso"""
        url = f"{self.base_url}/oauth2/token"
        data = {
            'grant_type': 'client_credentials',
            'client_id': self.client_id,
            'client_secret': self.client_secret
        }
        response = requests.post(url, data=data)
        response.raise_for_status()
        self.token = response.json()['access_token']
        return self.token
    
    def get_headers(self):
        """Headers con autenticación"""
        if not self.token:
            self.authenticate()
        return {
            'Authorization': f'Bearer {self.token}',
            'Content-Type': 'application/json'
        }
    
    def list_servers(self, **filters):
        """Listar servidores"""
        url = f"{self.base_url}/api/servers"
        response = requests.get(url, headers=self.get_headers(), params=filters)
        response.raise_for_status()
        return response.json()
    
    def get_server(self, server_id):
        """Obtener servidor específico"""
        url = f"{self.base_url}/api/servers/{server_id}"
        response = requests.get(url, headers=self.get_headers())
        response.raise_for_status()
        return response.json()
    
    def start_server(self, server_id):
        """Iniciar servidor"""
        url = f"{self.base_url}/api/servers/{server_id}/start"
        response = requests.post(url, headers=self.get_headers())
        response.raise_for_status()
        return True
    
    def stop_server(self, server_id):
        """Detener servidor"""
        url = f"{self.base_url}/api/servers/{server_id}/stop"
        response = requests.post(url, headers=self.get_headers())
        response.raise_for_status()
        return True
    
    def send_command(self, server_id, command):
        """Enviar comando a consola"""
        url = f"{self.base_url}/api/servers/{server_id}/console"
        data = {'command': command}
        response = requests.post(url, headers=self.get_headers(), json=data)
        response.raise_for_status()
        return True
    
    def create_server(self, server_data):
        """Crear servidor"""
        server_id = server_data.get('identifier', '')
        url = f"{self.base_url}/api/servers/{server_id}"
        response = requests.put(url, headers=self.get_headers(), json=server_data)
        response.raise_for_status()
        return response.json()

# Uso
api = SkyPanelAPI(
    base_url='http://localhost:8080',
    client_id='YOUR_CLIENT_ID',
    client_secret='YOUR_CLIENT_SECRET'
)

# Listar servidores
servers = api.list_servers(name='minecraft*')
print(f"Encontrados {len(servers['servers'])} servidores")

# Iniciar servidor
api.start_server('ABC12345')
print("Servidor iniciado")

# Enviar comando
api.send_command('ABC12345', 'say Hello from Python!')
print("Comando enviado")

Node.js

Instalación
npm install axios
Full Example
const axios = require('axios');

class SkyPanelAPI {
  constructor(baseURL, clientId, clientSecret) {
    this.baseURL = baseURL;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.token = null;
    
    this.client = axios.create({
      baseURL: this.baseURL
    });
  }
  
  async authenticate() {
    const response = await this.client.post('/oauth2/token', 
      new URLSearchParams({
        grant_type: 'client_credentials',
        client_id: this.clientId,
        client_secret: this.clientSecret
      }),
      {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        }
      }
    );
    
    this.token = response.data.access_token;
    return this.token;
  }
  
  getHeaders() {
    if (!this.token) {
      throw new Error('Not authenticated. Call authenticate() first.');
    }
    
    return {
      'Authorization': `Bearer ${this.token}`,
      'Content-Type': 'application/json'
    };
  }
  
  async listServers(filters = {}) {
    const response = await this.client.get('/api/servers', {
      headers: this.getHeaders(),
      params: filters
    });
    return response.data;
  }
  
  async getServer(serverId) {
    const response = await this.client.get(`/api/servers/${serverId}`, {
      headers: this.getHeaders()
    });
    return response.data;
  }
  
  async startServer(serverId) {
    await this.client.post(`/api/servers/${serverId}/start`, {}, {
      headers: this.getHeaders()
    });
    return true;
  }
  
  async stopServer(serverId) {
    await this.client.post(`/api/servers/${serverId}/stop`, {}, {
      headers: this.getHeaders()
    });
    return true;
  }
  
  async sendCommand(serverId, command) {
    await this.client.post(`/api/servers/${serverId}/console`, 
      { command },
      { headers: this.getHeaders() }
    );
    return true;
  }
  
  async createServer(serverData) {
    const serverId = serverData.identifier || '';
    const response = await this.client.put(`/api/servers/${serverId}`, 
      serverData,
      { headers: this.getHeaders() }
    );
    return response.data;
  }
}

// Uso
(async () => {
  const api = new SkyPanelAPI(
    'http://localhost:8080',
    'YOUR_CLIENT_ID',
    'YOUR_CLIENT_SECRET'
  );
  
  // Autenticar
  await api.authenticate();
  console.log('Autenticado');
  
  // Listar servidores
  const servers = await api.listServers({ name: 'minecraft*' });
  console.log(`Encontrados ${servers.servers.length} servidores`);
  
  // Iniciar servidor
  await api.startServer('ABC12345');
  console.log('Servidor iniciado');
  
  // Enviar comando
  await api.sendCommand('ABC12345', 'say Hello from Node.js!');
  console.log('Comando enviado');
})();

No olvides que Aether Panel es un proyecto en desarrollo open source, si tienes alguna duda o problema al instalar o el comando del instalador no funciona puedes contactarnos en el Discord de Aether Panel.

    Aether Panel | Open Source Game Server & Cloud Hosting Platform