Aether Panel Documentation

Introducción a la API

La API de Aether Panel (SkyPanel) es una API RESTful completa y potente que permite la automatización y gestión programática de todos los aspectos del panel. Está diseñada siguiendo los estándares de la industria y soporta OAuth2 para autenticación segura.

Con esta API puedes automatizar completamente la gestión de servidores, usuarios, nodos, bases de datos, backups y mucho más. Ideal para integraciones con sistemas de facturación, paneles de control personalizados, bots de Discord, o cualquier aplicación que necesite interactuar con Aether Panel.

URL Base de la API

Todas las peticiones a la API deben realizarse a la siguiente URL base:

http://tu-servidor:8080/api

https://panel.tudominio.com/api

En desarrollo, usa la IP o localhost. En producción, siempre usa HTTPS para seguridad.

Versionado

La API está versionada para garantizar compatibilidad. La versión actual es v1. Todas las respuestas incluyen el header `X-API-Version` con la versión de la API utilizada.

Autenticación

Las solicitudes a la API se autentican mediante Bearer Tokens. Puedes generar claves de API desde la configuración de tu cuenta en el panel. Todas las solicitudes a la API deben realizarse a través de HTTPS.

Características de la API
  • RESTful: Sigue principios REST estándar
  • OAuth2: Autenticación segura con tokens
  • JSON: Formato de datos JSON para requests y responses
  • Versionada: API versionada para compatibilidad
  • Documentada: Documentación completa con ejemplos
  • WebSocket: Soporte para comunicación en tiempo real
  • Paginación: Resultados paginados para grandes conjuntos de datos
  • Filtrado: Búsqueda y filtrado avanzado
Ejemplo de autenticación
https://dominio:8080/api
Autenticación con OAuth2

SkyPanel utiliza OAuth2 con el flujo de **Client Credentials** para autenticación de API.

Paso 1: Crear un Cliente OAuth2

Puedes crear un cliente OAuth2 desde:

  • Panel Web: Configuración → OAuth2 Clients
  • CLI: `skypanel oauth2 create`
Paso 2: Obtener Token de Acceso

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

Ejemplo con cURL

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"

Respuesta

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "server.view server.edit"
}
Paso 3: Usar el Token

Incluye el token en el header `Authorization` de todas las peticiones:.

http Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Ejemplo

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

Scopes definen los permisos que un cliente tiene para acceder a ciertos endpoints. Por ejemplo, un cliente con el scope `server.read` puede leer información de servidores, pero no puede crear o modificarlos.

PermisoDescripción
adminAcceso administrativo completo
server.viewVer servidores
server.createCrear servidores
server.editEditar servidores
server.deleteEliminar servidores
server.startIniciar servidores
server.stopDetener servidores
server.consoleAcceso a consola
server.files.viewVer archivos
server.files.editEditar archivos
users.viewVer usuarios
users.editEditar usuarios
nodes.viewVer nodos
nodes.editEditar nodos
Formato de Datos
Content-Type

Todas las peticiones y respuestas usan JSON:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "server.view server.edit"
}
Estructura de Respuesta Exitosa
{
  "data": { ... },
                "metadata": {
                    "paging": {
                    "page": 1,
                "size": 25,
                "maxSize": 100,
                "total": 150
    }
  }
}
Códigos de Estado HTTP
CódigoSignificadoDescripción
200OKPetición exitosa
201CreatedRecurso creado exitosamente
204No ContentPetición exitosa sin contenido de respuesta
400Bad RequestDatos de entrada inválidos
401UnauthorizedToken inválido o expirado
403ForbiddenSin permisos para esta acción
404Not FoundRecurso no encontrado
500Internal Server ErrorError del servidor
Manejo de Errores
Estructura de Error
{
  "error": {
    "code": "ErrServerNotFound",
    "msg": "Server with ID {id} not found",
    "metadata": {
      "id": "ABC12345"
    }
  }
}
Códigos de Error Comunes
CódigoDescripción
ErrFieldRequiredCampo requerido faltante
ErrFieldInvalidValor de campo inválido
ErrServerNotFoundServidor no encontrado
ErrUserNotFoundUsuario no encontrado
ErrNodeNotFoundNodo no encontrado
ErrPermissionDeniedPermiso denegado
ErrDatabaseErrorError de base de datos

Paginación

Los endpoints que retornan listas soportan paginación:

Parámetros de Query
ParámetroTipoDefaultDescripción
pageint1Número de página (1-indexed)
limitint25Elementos por página

Máximo: 100 elementos por página

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

Endpoints de Servidores

Listar Servidores

Endpoint: GET /api/servers

Scopes: server.view

Parámetros de Query
ParámetroTipoDescripción
usernamestringFiltrar por usuario (solo admin)
nodeuintFiltrar por ID de nodo
namestringFiltrar por nombre (soporta * como wildcard)
pageuintNúmero de página
limituintElementos por página
Ejemplo
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
    }
  }
}
Obtener Servidor

Endpoint: GET /api/servers/:serverId

Scopes: server.view

Parámetros
ParámetroTipoUbicaciónDescripción
serverIdstringPathID del servidor
permsbooleanQueryIncluir permisos del usuario
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"
  }
}
Crear Servidor

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"
}
Actualizar Definición de Servidor

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

Eliminar Servidor

Endpoint: DELETE /api/servers/:serverId

Scopes: server.delete

ParámetroTipoDescripción
skipNodebooleanNo eliminar del nodo, solo de la base de datos

Respuesta: 204 No Content

Iniciar Servidor

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

Scopes: server.start

Respuesta: 204 No Content

Detener Servidor

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

Scopes: server.stop

Respuesta: 204 No Content

Reiniciar Servidor

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

Scopes: server.start, server.stop

Respuesta: 204 No Content

Matar Servidor (Force Stop)

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

Scopes: server.kill

Respuesta: 204 No Content

Instalar Servidor

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

Scopes: server.install

Respuesta: 204 No Content

Obtener Estado del Servidor

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

Scopes: server.status

{
  "running": true
}
Obtener Estadísticas del Servidor

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

Scopes: server.stats

{
  "cpu": 45.2,
  "memory": 1536000000,
  "memoryTotal": 2147483648
}
Obtener Consola

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

Scopes: server.console

Parámetros
ParámetroTipoDescripción
timeintTimestamp desde el cual obtener logs
Respuesta
{
  "logs": [
    "[10:30:15] [Server thread/INFO]: Starting minecraft server version 1.20.1",
    "[10:30:16] [Server thread/INFO]: Loading properties"
  ]
}
Enviar Comando a Consola

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

Scopes: server.sendCommand

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

Respuesta: 204 No Content

Gestión de Archivos

Listar Archivos

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

Scopes: server.files.view

Ejemplo
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
    }
  ]
}
Descargar Archivo

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
Subir Archivo

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
Eliminar Archivo

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

Listar Backups

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

Scopes: server.backup.view

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

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

Scopes: server.backup.create

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

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

Scopes: server.backup.restore

Respuesta: 204 No Content

Eliminar Backup

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

Scopes: server.backup.delete

Respuesta: 204 No Content

Descargar 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

Usuarios

Listar Usuarios

Endpoint: GET /api/users

Scopes: users.info.search

Ejemplo
curl -X GET "http://localhost:8080/api/users?username=admin*" \
  -H "Authorization: Bearer YOUR_TOKEN"
Crear Usuario

Endpoint: POST /api/users

Scopes: users.info.edit

{
  "username": "newuser",
  "email": "newuser@example.com",
  "password": "SecurePassword123!"
}
Obtener Usuario

Endpoint: GET /api/users/:id

Scopes: users.info.view

Actualizar Usuario

Endpoint: POST /api/users/:id

Scopes: users.info.edit

Eliminar Usuario

Endpoint: DELETE /api/users/:id

Scopes: users.info.edit

Respuesta: 204 No Content

Obtener Permisos de Usuario

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

Scopes: users.perms.view

[
  "server.view",
  "server.console"
]
Actualizar Permisos de Usuario

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

Nodos

Listar Nodos

Endpoint: GET /api/nodes

Scopes: nodes.view

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

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..."
}
Obtener Nodo

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
}
Actualizar Nodo

Endpoint: PUT /api/nodes/:id

Scopes: nodes.edit

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

Respuesta: 204 No Content

Eliminar Nodo

Endpoint: DELETE /api/nodes/:id

Scopes: nodes.delete

Respuesta: 204 No Content

Obtener Información del Sistema del Nodo

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"
  }
}
Obtener Features del Nodo

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

Scopes: nodes.view

Respuesta
{
  "docker": true,
  "environments": [
    "standard",
    "docker"
  ]
}
Obtener Datos de Deployment

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

Scopes: nodes.deploy

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

Configuración

Obtener Configuración

Endpoint: GET /api/settings

Scopes: settings.view

{
  "companyName": "SkyPanel",
  "defaultTheme": "SkyPanel",
  "masterUrl": "https://panel.example.com",
  "registrationEnabled": false
}
Actualizar Configuración

Endpoint: PUT /api/settings

Scopes: settings.edit

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

Respuesta: 204 No Content

Plantillas

Listar Plantillas

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"]
    }
  ]
}
Obtener Plantilla

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

Conectar a Consola en Tiempo Real

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));
};
Tipos de Mensajes WebSocket
  • Autenticación: { "type": "auth", "token": "..." }
  • Consola (Servidor -> Cliente): { "type": "console", "data": "..." }
  • Comando (Cliente -> Servidor): { "type": "console", "data": "command" }
  • Estadísticas (Servidor -> Cliente): { "type": "stats", "data": { ... } }
  • Estado (Servidor -> Cliente): { "type": "status", "data": { ... } }

Bases de Datos de Servidor

Estos endpoints permiten gestionar bases de datos MySQL/MariaDB asociadas a servidores específicos.

Listar Bases de Datos

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

Scopes: server.view

Obtiene todas las bases de datos creadas para un servidor específico.

Crear Base de Datos

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

Scopes: server.edit

Crea una nueva base de datos MySQL/MariaDB para el servidor. El panel genera automáticamente el nombre de la base de datos, usuario y contraseña.

Body
  • databaseHostId: ID del Database Host a usar (requerido)
  • databaseName: Nombre personalizado de la base de datos (opcional, se genera automáticamente si se omite)
Eliminar Base de Datos

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

Scopes: server.edit

Elimina una base de datos y su usuario asociado del servidor MySQL/MariaDB.

Database Hosts

Los Database Hosts son configuraciones de servidores MySQL/MariaDB que el panel puede usar para crear bases de datos automáticamente.

Listar Database Hosts

Endpoint: GET /api/databasehosts

Scopes: admin

Obtiene todos los Database Hosts configurados en el panel.

Crear Database Host

Endpoint: POST /api/databasehosts

Scopes: admin

Crea un nuevo Database Host. Requiere credenciales de MySQL con permisos GRANT ALL PRIVILEGES.

Body
  • name: Nombre del host (requerido)
  • host: IP o dominio del servidor MySQL (requerido)
  • port: Puerto MySQL (por defecto 3306)
  • username: Usuario MySQL con permisos (requerido)
  • password: Contraseña del usuario (requerido)
  • maxDatabases: Máximo número de bases de datos (opcional, sin límite si se omite)
  • nodeId: ID del nodo vinculado (opcional)

Límites de Tasa (Rate Limiting)

La API implementa límites de tasa para prevenir abuso y asegurar un rendimiento óptimo para todos los usuarios.

Límites Actuales
  • Autenticado: 1000 peticiones por hora por cliente OAuth2
  • No autenticado: 100 peticiones por hora por IP

Los límites se aplican por cliente OAuth2 o por IP para peticiones no autenticadas.

Mejores Prácticas

Sigue estas recomendaciones para usar la API de manera eficiente y segura:

  • Usa HTTPS en producción: Nunca envíes tokens o credenciales a través de HTTP sin cifrar.
  • Almacena tokens de forma segura: No hardcodees tokens en tu código. Usa variables de entorno o sistemas de gestión de secretos.
  • Maneja errores apropiadamente: Siempre verifica los códigos de estado HTTP y maneja errores de forma elegante.
  • Implementa reintentos con backoff exponencial: Para peticiones que fallan temporalmente, implementa lógica de reintento.
  • Usa paginación para listas grandes: No intentes obtener todos los resultados de una vez. Usa paginación para mejorar el rendimiento.
  • Cachea respuestas cuando sea apropiado: Algunos datos no cambian frecuentemente y pueden ser cacheados.
  • Respeta los límites de tasa: Implementa throttling en tu aplicación para no exceder los límites.
  • Valida datos antes de enviarlos: Verifica que los datos cumplen con los requisitos antes de hacer peticiones.
  • Usa WebSockets para datos en tiempo real: Para consolas y estadísticas, usa WebSockets en lugar de polling constante.
  • Mantén tus tokens seguros: Rota tus client secrets regularmente y nunca los compartas públicamente.

Solución de Problemas

Problemas Comunes

Error 401 Unauthorized

Tu token ha expirado o es inválido. Obtén un nuevo token usando el endpoint /oauth2/token.

Error 403 Forbidden

Tu cliente OAuth2 no tiene los scopes necesarios para esta acción. Verifica los scopes asignados a tu cliente.

Error 404 Not Found

El recurso que intentas acceder no existe. Verifica que el ID sea correcto y que el recurso no haya sido eliminado.

Error 400 Bad Request

Los datos enviados no son válidos. Revisa la estructura del body y los tipos de datos requeridos.

Error 429 Too Many Requests

Has excedido el límite de tasa. Espera hasta que se reinicie el contador o implementa throttling en tu aplicación.

Ejemplos de Uso

Ejemplos Prácticos

A continuación encontrarás ejemplos completos de cómo usar la API en diferentes lenguajes de programación. Estos ejemplos cubren los casos de uso más comunes.

Casos de Uso Comunes

Automatización de Servidores

Crea scripts para iniciar/detener servidores automáticamente según horarios o eventos.

Integración con Sistemas de Facturación

Conecta Aether Panel con sistemas como WHMCS, Blesta, o soluciones personalizadas para crear servidores automáticamente cuando un cliente realiza un pago.

Monitoreo y Alertas

Crea dashboards personalizados o integra con sistemas de monitoreo como Grafana, Prometheus, etc.

Backups Automáticos

Programa backups automáticos de tus servidores usando cron jobs o sistemas de scheduling.

Bots de Discord

Crea bots de Discord que permitan a los usuarios gestionar sus servidores desde Discord usando comandos.

Ejemplo con cURL

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
Ejemplo Completo
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
Ejemplo Completo
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.