REST API

Shulker API v2.1.0

Programmatically control Shulker's high-performance gaming infrastructure. Manage servers, containers, teams, and files through a unified REST API with enterprise-grade features.

Updated recently 35 min read Production Ready

Introduction

Server Management
Start, stop, restart Minecraft servers and DevSpace containers with millisecond response times.
File Operations
Upload, download, edit, and manage server files through our secure file API.
Team Collaboration
Manage teams, share server access, and control permissions programmatically.
Base URL
https://shulker.in/api/v2/

Authentication

All API requests require authentication via API keys. Keys provide granular permissions and rate limiting.

Query Parameter (Recommended)
?api_key=sk_1e14aecda1a06bc22062f8fcb8e90cbde309d29d1e7955378b1aeaf6c130cdc3
Header Method
X-API-Key: sk_1e14aecda1a06bc22062f8fcb8e90cbde309d29d1e7955378b1aeaf6c130cdc3

🔐 Security Note — API keys grant access to your infrastructure. Treat them like passwords. Never commit API keys to version control. Use environment variables or secret management. Rotate keys regularly (every 90 days recommended).

Permissions System

Granular permission system with read and write categories. Each API key can have specific permissions assigned.

Read Permissions
info, servers, server_status, server_logs, server_console, wallet_info, team_info, public_servers, all
Write Permissions
server_start, server_stop, server_restart, server_command, file_write, file_upload, team_create, all
Check Permissions
curl "https://shulker.in/api/v2/?req=permissions&api_key=sk_..."

Rate Limits

60 per Minute
Per API key
1,000 per Hour
Per API key
10,000 per Day
Per API key
Rate Limit Headers
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1697385645

Error Handling

Status CodeMeaning
200 OKRequest successful
400 Bad RequestInvalid parameters
401 UnauthorizedInvalid API key
403 ForbiddenInsufficient permissions
404 Not FoundResource not found
429 Too Many RequestsRate limit exceeded
500 Internal ErrorServer error
Error Response
{
    "error": "Authorization failed",
    "message": "Insufficient permissions",
    "code": 403,
    "timestamp": "2026-01-07T16:28:03+05:30",
    "request_id": "req_659a1b2c3d4e5f"
}

Minecraft Server Management

POST / GET server_start
Start Server
curl "https://shulker.in/api/v2/?req=minecraft&action=server_start&server_name=MyServer&node=de0&api_key=sk_..."
POST / GET server_stop
Stop Server
curl "https://shulker.in/api/v2/?req=minecraft&action=server_stop&server_name=MyServer&node=de0&api_key=sk_..."
POST / GET server_restart
Restart Server
curl "https://shulker.in/api/v2/?req=minecraft&action=server_restart&server_name=MyServer&node=de0&api_key=sk_..."
POST / GET server_command
Execute Command
curl "https://shulker.in/api/v2/?req=minecraft&action=server_command&server_name=MyServer&node=de0&cmd=say%20Hello%20World&api_key=sk_..."
GET server_status
Get Status
curl "https://shulker.in/api/v2/?req=minecraft&action=server_status&server_name=MyServer&node=de0&api_key=sk_..."
GET server_logs
Get Logs
curl "https://shulker.in/api/v2/?req=minecraft&action=server_logs&server_name=MyServer&node=de0&lines=100&api_key=sk_..."
GET servers
List Servers
curl "https://shulker.in/api/v2/?req=servers&api_key=sk_..."

DevSpace Containers

Start Container
curl "https://shulker.in/api/v2/?req=devspace&action=server_start&server_name=my-node-app&node=us1&api_key=sk_..."
Stop Container
curl "https://shulker.in/api/v2/?req=devspace&action=server_stop&server_name=my-node-app&node=us1&api_key=sk_..."
Execute Command
curl "https://shulker.in/api/v2/?req=devspace&action=server_command&server_name=my-node-app&node=us1&cmd=npm%20install&api_key=sk_..."
Get Logs
curl "https://shulker.in/api/v2/?req=devspace&action=server_logs&server_name=my-node-app&node=us1&api_key=sk_..."

File Operations

List Files
curl "https://shulker.in/api/v2/?req=minecraft&action=file_list&server_name=MyServer&node=de0&path=/&api_key=sk_..."
Read File
curl "https://shulker.in/api/v2/?req=minecraft&action=file_read&server_name=MyServer&node=de0&path=server.properties&api_key=sk_..."
Write File
curl -X POST https://shulker.in/api/v2/ \
  -H "Content-Type: application/json" \
  -d '{
    "req": "minecraft",
    "action": "file_write",
    "server_name": "MyServer",
    "node": "de0",
    "path": "server.properties",
    "content": "max-players=50\\ndifficulty=hard\\nmotd=My Awesome Server",
    "api_key": "sk_..."
  }'
Upload File
curl -X POST https://shulker.in/api/v2/ \
  -F "req=minecraft" \
  -F "action=file_upload" \
  -F "server_name=MyServer" \
  -F "node=de0" \
  -F "path=plugins/" \
  -F "api_key=sk_..." \
  -F "[email protected]"
Delete File
curl -X DELETE "https://shulker.in/api/v2/?req=minecraft&action=file_delete&server_name=MyServer&node=de0&path=oldfile.txt&api_key=sk_..."

User Management

Get User Info
curl "https://shulker.in/api/v2/?req=info&api_key=sk_..."
Get Wallet Info
curl "https://shulker.in/api/v2/?req=wallet&api_key=sk_..."

Team Management

List Teams
curl "https://shulker.in/api/v2/?req=teams&api_key=sk_..."
Create Team
curl -X POST https://shulker.in/api/v2/ \
  -H "Content-Type: application/json" \
  -d '{
    "req": "teams",
    "team_name": "My Development Team",
    "initial_funds": 100,
    "api_key": "sk_..."
  }'
Add Member to Team
curl -X POST https://shulker.in/api/v2/ \
  -H "Content-Type: application/json" \
  -d '{
    "req": "teams",
    "action": "add_member",
    "team_id": 1,
    "user_email": "[email protected]",
    "api_key": "sk_..."
  }'

API Key Management

List API Keys
curl "https://shulker.in/api/v2/?req=api_keys&api_key=sk_..."
Create API Key
curl -X POST https://shulker.in/api/v2/ \
  -H "Content-Type: application/json" \
  -d '{
    "req": "api_keys",
    "name": "Monitoring Bot",
    "description": "Server monitoring and alerting",
    "permissions": {
      "read": ["servers", "server_status", "server_logs", "server_console"],
      "write": []
    },
    "server_restrictions": ["prod-*", "monitoring-*"],
    "ip_restrictions": ["192.168.1.0/24", "10.0.0.0/8"],
    "expires_at": "2026-04-01",
    "rate_limit_per_minute": 30,
    "rate_limit_per_hour": 500,
    "rate_limit_per_day": 5000,
    "api_key": "sk_..."
  }'
Update API Key
curl -X PUT https://shulker.in/api/v2/ \
  -H "Content-Type: application/json" \
  -d '{
    "req": "api_keys",
    "action": "update",
    "key_id": 3,
    "name": "Updated Bot Name",
    "permissions": {
      "read": ["servers", "server_status", "server_logs", "server_console", "server_stats"],
      "write": []
    },
    "api_key": "sk_..."
  }'
Delete API Key
curl -X DELETE "https://shulker.in/api/v2/?req=api_keys&key_id=3&api_key=sk_..."
Rotate API Key
curl -X POST https://shulker.in/api/v2/ \
  -H "Content-Type: application/json" \
  -d '{
    "req": "api_keys",
    "action": "rotate",
    "key_id": 3,
    "api_key": "sk_..."
  }'

Code Examples

Python Client
import requests
import json
from typing import Optional, Dict, Any

class ShulkerAPI:
    def __init__(self, api_key: str, base_url: str = "https://shulker.in/api/v2/"):
        self.api_key = api_key
        self.base_url = base_url
        self.session = requests.Session()

    def _request(self, params: Dict[str, Any], method: str = "GET") -> Dict[str, Any]:
        params['api_key'] = self.api_key
        url = self.base_url
        
        if method.upper() == "GET":
            response = self.session.get(url, params=params)
        else:
            response = self.session.post(url, json=params)
        
        response.raise_for_status()
        return response.json()

    def get_user_info(self) -> Dict[str, Any]:
        return self._request({'req': 'info'})

    def list_servers(self) -> Dict[str, Any]:
        return self._request({'req': 'servers'})

    def start_server(self, server_name: str, node: str) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'server_start',
            'server_name': server_name,
            'node': node
        })

    def stop_server(self, server_name: str, node: str) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'server_stop',
            'server_name': server_name,
            'node': node
        })

    def restart_server(self, server_name: str, node: str) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'server_restart',
            'server_name': server_name,
            'node': node
        })

    def execute_command(self, server_name: str, node: str, command: str) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'server_command',
            'server_name': server_name,
            'node': node,
            'cmd': command
        })

    def get_server_status(self, server_name: str, node: str) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'server_status',
            'server_name': server_name,
            'node': node
        })

    def get_server_logs(self, server_name: str, node: str, lines: int = 100) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'server_logs',
            'server_name': server_name,
            'node': node,
            'lines': lines
        })

    def list_files(self, server_name: str, node: str, path: str = "/") -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'file_list',
            'server_name': server_name,
            'node': node,
            'path': path
        })

    def read_file(self, server_name: str, node: str, file_path: str) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'file_read',
            'server_name': server_name,
            'node': node,
            'path': file_path
        })

    def write_file(self, server_name: str, node: str, file_path: str, content: str) -> Dict[str, Any]:
        return self._request({
            'req': 'minecraft',
            'action': 'file_write',
            'server_name': server_name,
            'node': node,
            'path': file_path,
            'content': content
        }, method="POST")

    def create_api_key(self, name: str, permissions: Dict, **kwargs) -> Dict[str, Any]:
        data = {
            'req': 'api_keys',
            'name': name,
            'permissions': permissions,
            **kwargs
        }
        return self._request(data, method="POST")

    def delete_api_key(self, key_id: int) -> Dict[str, Any]:
        return self._request({
            'req': 'api_keys',
            'key_id': key_id
        }, method="DELETE")

# Usage Example
if __name__ == "__main__":
    client = ShulkerAPI("sk_your_api_key_here")
    
    # Get user information
    user = client.get_user_info()
    print(f"Logged in as: {user['user']['email']}")
    
    # List all servers
    servers = client.list_servers()
    for server in servers['servers']:
        print(f"Server: {server['server_name']} - Status: {server['server_status']}")
    
    # Start a server
    result = client.start_server("MySurvivalServer", "de0")
    print(f"Start result: {result['message']}")
    
    # Execute a command
    cmd_result = client.execute_command("MySurvivalServer", "de0", "say Server started via API!")
    print(f"Command result: {cmd_result['success']}")
JavaScript/Node.js Client
class ShulkerAPI {
  constructor(apiKey, baseUrl = 'https://shulker.in/api/v2/') {
    this.apiKey = apiKey;
    this.baseUrl = baseUrl;
  }

  async request(params, method = 'GET') {
    params.api_key = this.apiKey;
    const url = new URL(this.baseUrl);
    
    if (method === 'GET') {
      url.search = new URLSearchParams(params).toString();
      const response = await fetch(url);
      if (!response.ok) throw new Error(await response.text());
      return response.json();
    } else {
      const response = await fetch(url, {
        method: method,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(params)
      });
      if (!response.ok) throw new Error(await response.text());
      return response.json();
    }
  }

  async getUserInfo() {
    return this.request({ req: 'info' });
  }

  async listServers() {
    return this.request({ req: 'servers' });
  }

  async startServer(serverName, node) {
    return this.request({
      req: 'minecraft',
      action: 'server_start',
      server_name: serverName,
      node: node
    });
  }

  async stopServer(serverName, node) {
    return this.request({
      req: 'minecraft',
      action: 'server_stop',
      server_name: serverName,
      node: node
    });
  }

  async restartServer(serverName, node) {
    return this.request({
      req: 'minecraft',
      action: 'server_restart',
      server_name: serverName,
      node: node
    });
  }

  async executeCommand(serverName, node, command) {
    return this.request({
      req: 'minecraft',
      action: 'server_command',
      server_name: serverName,
      node: node,
      cmd: command
    });
  }

  async getServerStatus(serverName, node) {
    return this.request({
      req: 'minecraft',
      action: 'server_status',
      server_name: serverName,
      node: node
    });
  }

  async getServerLogs(serverName, node, lines = 100) {
    return this.request({
      req: 'minecraft',
      action: 'server_logs',
      server_name: serverName,
      node: node,
      lines: lines
    });
  }

  async listFiles(serverName, node, path = '/') {
    return this.request({
      req: 'minecraft',
      action: 'file_list',
      server_name: serverName,
      node: node,
      path: path
    });
  }

  async readFile(serverName, node, filePath) {
    return this.request({
      req: 'minecraft',
      action: 'file_read',
      server_name: serverName,
      node: node,
      path: filePath
    });
  }

  async createAPIKey(name, permissions, options = {}) {
    return this.request({
      req: 'api_keys',
      name: name,
      permissions: permissions,
      ...options
    }, 'POST');
  }

  async deleteAPIKey(keyId) {
    return this.request({
      req: 'api_keys',
      key_id: keyId
    }, 'DELETE');
  }
}

// Usage Example
async function main() {
  const client = new ShulkerAPI('sk_your_api_key_here');
  
  try {
    // Get user info
    const user = await client.getUserInfo();
    console.log(`Logged in as: ${user.user.email}`);
    
    // List servers
    const servers = await client.listServers();
    servers.servers.forEach(server => {
      console.log(`Server: ${server.server_name} - Status: ${server.server_status}`);
    });
    
    // Start a server
    const result = await client.startServer('MySurvivalServer', 'de0');
    console.log(`Start result: ${result.message}`);
  } catch (error) {
    console.error('Error:', error.message);
  }
}

main();
Bash Script
#!/bin/bash

# Configuration
API_KEY="sk_your_api_key_here"
BASE_URL="https://shulker.in/api/v2/"
SERVER_NAME="MySurvivalServer"
NODE="de0"
LOG_FILE="/var/log/shulker-api.log"

# Logging function
log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

# Make API request
make_request() {
    local params="$1"
    local url="${BASE_URL}?${params}&api_key=${API_KEY}"
    curl -s -X GET "$url"
}

# Check server status
check_status() {
    log_message "Checking status of server: $SERVER_NAME"
    local response=$(make_request "req=minecraft&action=server_status&server_name=${SERVER_NAME}&node=${NODE}")
    local status=$(echo "$response" | jq -r '.data.server_status // "unknown"' 2>/dev/null)
    
    if [[ "$status" == *"Running"* ]]; then
        log_message "Server is running"
        echo "✅ Server is running"
        return 0
    else
        log_message "Server is not running: $status"
        echo "❌ Server is not running"
        return 1
    fi
}

# Start server
start_server() {
    log_message "Starting server: $SERVER_NAME"
    local response=$(make_request "req=minecraft&action=server_start&server_name=${SERVER_NAME}&node=${NODE}")
    local success=$(echo "$response" | jq -r '.success // false' 2>/dev/null)
    
    if [[ "$success" == "true" ]]; then
        log_message "Server started successfully"
        echo "✅ Server started successfully"
    else
        log_message "Failed to start server: $response"
        echo "❌ Failed to start server"
    fi
}

# Stop server
stop_server() {
    log_message "Stopping server: $SERVER_NAME"
    local response=$(make_request "req=minecraft&action=server_stop&server_name=${SERVER_NAME}&node=${NODE}")
    local success=$(echo "$response" | jq -r '.success // false' 2>/dev/null)
    
    if [[ "$success" == "true" ]]; then
        log_message "Server stopped successfully"
        echo "✅ Server stopped successfully"
    else
        log_message "Failed to stop server: $response"
        echo "❌ Failed to stop server"
    fi
}

# Send command
send_command() {
    local cmd="$1"
    log_message "Sending command: $cmd"
    local encoded_cmd=$(printf '%s' "$cmd" | jq -sRr @uri)
    local response=$(make_request "req=minecraft&action=server_command&server_name=${SERVER_NAME}&node=${NODE}&cmd=${encoded_cmd}")
    echo "$response" | jq '.'
}

# Main menu
case "$1" in
    status)
        check_status
        ;;
    start)
        start_server
        ;;
    stop)
        stop_server
        ;;
    restart)
        stop_server
        sleep 5
        start_server
        ;;
    command)
        if [[ -z "$2" ]]; then
            echo "Usage: $0 command \"\""
            exit 1
        fi
        send_command "$2"
        ;;
    *)
        echo "Usage: $0 {status|start|stop|restart|command}"
        echo "  status   - Check server status"
        echo "  start    - Start the server"
        echo "  stop     - Stop the server"
        echo "  restart  - Restart the server"
        echo "  command  - Send a command to the server"
        exit 1
        ;;
esac

Best Practices

🔐 Security — Use environment variables for API keys, never commit keys to version control, implement IP restrictions, rotate keys every 90 days, and always apply the principle of least privilege when assigning permissions.

⚡ Performance — Implement caching for frequently accessed data (server status, user info), batch operations when possible, use appropriate polling intervals for status checks, and consider webhooks for real-time updates instead of polling.

🔄 Error Handling — Implement exponential backoff with jitter for 5xx errors and rate limits (429), use circuit breaker pattern to prevent cascade failures, log all errors with request IDs for debugging, and set up alerting for persistent failures.

📝 Development — Build or use client libraries for your language, always test API changes in staging first, specify API version in requests, and keep internal API usage documentation updated.

Changelog

v2.1.0 (January 2025) — Added comprehensive API key management endpoints (create, list, update, delete, rotate). Enhanced permission system with granular read/write controls. Added server expiry checks for start/restart operations. Implemented IP and server restrictions for API keys. Added rate limit per server per day functionality. Enhanced error responses with detailed permission information. Improved request logging with Cloudflare integration. Added request ID tracking for better debugging.

v2.0.0 (December 2024) — Complete rewrite of API architecture. Unified endpoint structure for Minecraft and DevSpace. Added team management capabilities (create teams, add members, manage permissions). Implemented advanced permission system with read/write categories. Added comprehensive file operation endpoints (list, read, write, upload, delete). Optimized database queries for better performance. Enhanced CORS headers for better cross-origin support.

⚠️ Deprecation Notice — API v1.x will be sunset on June 30, 2026. Please migrate to v2.x before this date. All v1.x endpoints will continue to work until the sunset date, but new features will only be available in v2.x. View the migration guide at /docs/migration-guide