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.
All API requests must be made to the following base URL:
http://tu-servidor:8080/apihttps://panel.tudominio.com/api
In development, use IP or localhost. In production, always use HTTPS for security.
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.
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.
- 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
https://dominio:8080/apiSkyPanel uses OAuth2 with the **Client Credentials** flow for API authentication.
You can create an OAuth2 client from:
- Web Panel: Settings → OAuth2 Clients
- CLI: `skypanel oauth2 create`
Endpoint
POST /oauth2/tokenHeaders
http Content-Type: application/x-www-form-urlencodedBody
grant_type=client_credentials client_id=TU_CLIENT_ID client_secret=TU_CLIENT_SECRETcURL 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"
}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 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.
| Scope | Description |
|---|---|
| admin | Full administrative access |
| server.view | View servers |
| server.create | Create servers |
| server.edit | Edit servers |
| server.delete | Delete servers |
| server.start | Start servers |
| server.stop | Stop servers |
| server.console | Console access |
| server.files.view | View files |
| server.files.edit | Edit files |
| users.view | View users |
| users.edit | Edit users |
| nodes.view | View nodes |
| nodes.edit | Edit nodes |
All requests and responses use JSON:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "server.view server.edit"
}{
"data": { ... },
"metadata": {
"paging": {
"page": 1,
"size": 25,
"maxSize": 100,
"total": 150
}
}
}| Code | Meaning | Description |
|---|---|---|
| 200 | OK | Successful request |
| 201 | Created | Resource created successfully |
| 204 | No Content | Successful request with no response content |
| 400 | Bad Request | Invalid input data |
| 401 | Unauthorized | Invalid or expired token |
| 403 | Forbidden | No permission for this action |
| 404 | Not Found | Resource not found |
| 500 | Internal Server Error | Server error |
{
"error": {
"code": "ErrServerNotFound",
"msg": "Server with ID {id} not found",
"metadata": {
"id": "ABC12345"
}
}
}| Code | Description |
|---|---|
| ErrFieldRequired | Missing required field |
| ErrFieldInvalid | Invalid field value |
| ErrServerNotFound | Server not found |
| ErrUserNotFound | User not found |
| ErrNodeNotFound | Node not found |
| ErrPermissionDenied | Permission denied |
| ErrDatabaseError | Database error |
Pagination
Endpoints that return lists support pagination:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | int | 1 | Page number (1-indexed) |
| limit | int | 25 | Items per page |
Maximum: 100 items per page
GET /api/servers?page=2&limit=50{
"servers": [...],
"metadata": {
"paging": {
"page": 2,
"size": 50,
"maxSize": 100,
"total": 237
}
}
}Server Endpoints
Endpoint: GET /api/servers
Scopes: server.view
| Parámetro | Tipo | Descripción |
|---|---|---|
| username | string | Filter by user (admin only) |
| node | uint | Filter by node ID |
| name | string | Filter by name (supports * as wildcard) |
| page | uint | Page number |
| limit | uint | Items per page |
curl -X GET "http://localhost:8080/api/servers?username=admin*" \
-H "Authorization: Bearer YOUR_TOKEN"{
"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
}
}
}Endpoint: GET /api/servers/:serverId
Scopes: server.view
| Parámetro | Tipo | Ubicación | Descripción |
|---|---|---|---|
| serverId | string | Path | Server ID |
| perms | boolean | Query | Include user permissions |
{
"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"
}
}Endpoint: PUT /api/servers/:serverId
Scopes: server.create
{
"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
}
}{
"id": 2,
"uuid": "def67890...",
"name": "New Server",
"status": "installing"
}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
Endpoint: DELETE /api/servers/:serverId
Scopes: server.delete
| Parámetro | Tipo | Descripción |
|---|---|---|
| skipNode | boolean | Do not delete from node, only from database |
Respuesta: 204 No Content
Endpoint: POST /api/servers/:serverId/start
Scopes: server.start
Respuesta: 204 No Content
Endpoint: POST /api/servers/:serverId/stop
Scopes: server.stop
Respuesta: 204 No Content
Endpoint: POST /api/servers/:serverId/restart
Scopes: server.start, server.stop
Respuesta: 204 No Content
Endpoint: POST /api/servers/:serverId/kill
Scopes: server.kill
Respuesta: 204 No Content
Endpoint: POST /api/servers/:serverId/install
Scopes: server.install
Respuesta: 204 No Content
Endpoint: GET /api/servers/:serverId/status
Scopes: server.status
{
"running": true
}Endpoint: GET /api/servers/:serverId/stats
Scopes: server.stats
{
"cpu": 45.2,
"memory": 1536000000,
"memoryTotal": 2147483648
}Endpoint: GET /api/servers/:serverId/console
Scopes: server.console
| Parámetro | Tipo | Descripción |
|---|---|---|
| time | int | Timestamp to get logs from |
{
"logs": [
"[10:30:15] [Server thread/INFO]: Starting minecraft server version 1.20.1",
"[10:30:16] [Server thread/INFO]: Loading properties"
]
}Endpoint: POST /api/servers/:serverId/console
Scopes: server.sendCommand
{
"command": "say Hello World!"
}Respuesta: 204 No Content
File Management
Endpoint: GET /api/servers/:serverId/file/*filename
Scopes: server.files.view
curl -X GET "http://localhost:8080/api/servers/ABC12345/file/" \
-H "Authorization: Bearer YOUR_TOKEN"{
"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
}
]
}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.propertiesEndpoint: 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.ymlEndpoint: 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
Endpoint: GET /api/servers/:serverId/backup
Scopes: server.backup.view
{
"backups": [
{
"id": "backup-20240115-103000",
"size": 123456789,
"created": "2024-01-15T10:30:00Z"
}
]
}Endpoint: POST /api/servers/:serverId/backup/create
Scopes: server.backup.create
{
"id": "backup-20240115-120000"
}Endpoint: POST /api/servers/:serverId/backup/restore/:backupId
Scopes: server.backup.restore
Respuesta: 204 No Content
Endpoint: DELETE /api/servers/:serverId/backup/:backupId
Scopes: server.backup.delete
Respuesta: 204 No Content
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.gzUsers
Endpoint: GET /api/users
Scopes: users.info.search
curl -X GET "http://localhost:8080/api/users?username=admin*" \
-H "Authorization: Bearer YOUR_TOKEN"Endpoint: POST /api/users
Scopes: users.info.edit
{
"username": "newuser",
"email": "newuser@example.com",
"password": "SecurePassword123!"
}Endpoint: GET /api/users/:id
Scopes: users.info.view
Endpoint: POST /api/users/:id
Scopes: users.info.edit
Endpoint: DELETE /api/users/:id
Scopes: users.info.edit
Respuesta: 204 No Content
Endpoint: GET /api/users/:id/perms
Scopes: users.perms.view
[
"server.view",
"server.console"
]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
Endpoint: GET /api/nodes
Scopes: nodes.view
[
{
"id": 1,
"name": "Node-01",
"publicHost": "node1.example.com"
}
]Endpoint: POST /api/nodes
Scopes: nodes.create
{
"name": "Node-02",
"publicHost": "node2.example.com",
"privateHost": "192.168.1.11",
"publicPort": 8080,
"privatePort": 8080,
"sftpPort": 5657
}{
"id": 2,
"name": "Node-02",
"publicHost": "node2.example.com",
"privateHost": "192.168.1.11",
"publicPort": 8080,
"privatePort": 8080,
"sftpPort": 5657,
"secret": "abc123def456..."
}Endpoint: GET /api/nodes/:id
Scopes: nodes.view
{
"id": 1,
"name": "Node-01",
"publicHost": "node1.example.com",
"privateHost": "192.168.1.10",
"publicPort": 8080,
"privatePort": 8080,
"sftpPort": 5657
}Endpoint: PUT /api/nodes/:id
Scopes: nodes.edit
{
"name": "Node-01-Updated",
"publicHost": "node1-new.example.com"
}Respuesta: 204 No Content
Endpoint: DELETE /api/nodes/:id
Scopes: nodes.delete
Respuesta: 204 No Content
Endpoint: GET /api/nodes/:id/system
Scopes: nodes.view
{
"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"
}
}Endpoint: GET /api/nodes/:id/features
Scopes: nodes.view
{
"docker": true,
"environments": [
"standard",
"docker"
]
}Endpoint: GET /api/nodes/:id/deployment
Scopes: nodes.deploy
{
"clientId": ".node_1",
"clientSecret": "abc123def456..."
}Configuration
Endpoint: GET /api/settings
Scopes: settings.view
{
"companyName": "SkyPanel",
"defaultTheme": "SkyPanel",
"masterUrl": "https://panel.example.com",
"registrationEnabled": false
}Endpoint: PUT /api/settings
Scopes: settings.edit
{
"companyName": "Mi Empresa",
"registrationEnabled": true
}Respuesta: 204 No Content
Templates
Endpoint: GET /api/templates
Scopes: templates.view
{
"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"]
}
]
}Endpoint: GET /api/templates/:name
Scopes: templates.view
{
"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
Endpoint: WS /api/servers/:serverId/socket
Protocol: WebSocket
Scopes: server.view
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));
};- 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.
Endpoint: GET /api/servers/:serverId/databases
Scopes: server.view
Gets all databases created for a specific server.
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.
databaseHostId: Database Host ID to use (required)databaseName: Custom database name (optional, automatically generated if omitted)
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.
Endpoint: GET /api/databasehosts
Scopes: admin
Gets all Database Hosts configured in the panel.
Endpoint: POST /api/databasehosts
Scopes: admin
Creates a new Database Host. Requires MySQL credentials with GRANT ALL PRIVILEGES permissions.
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.
- 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
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.
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
pip install requestsimport 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
npm install axiosconst 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.
