API Documentation - ahmadzein/portkeeper GitHub Wiki
API Documentation
Port Keeper provides programmatic interfaces for integration with your applications and automation scripts.
🔌 Integration Methods
1. CLI with JSON Output
Most straightforward for scripting and automation.
2. Node.js Direct Integration
Import Port Keeper modules directly (advanced).
3. Child Process Execution
Run CLI commands from any programming language.
📡 CLI JSON API
All CLI commands support --json
flag for structured output.
Response Format
Success Response
{
"success": true,
"data": { ... },
"message": "Operation completed"
}
Error Response
{
"success": false,
"error": "Error message",
"code": "ERROR_CODE"
}
API Endpoints via CLI
Check Port Status
portman check 3000 --json
Response:
{
"success": true,
"data": {
"port": 3000,
"status": "free|reserved|in-use",
"project": "project-name",
"description": "Port description",
"pid": 12345,
"processName": "node"
}
}
Reserve Port
portman reserve 3000 -n "my-app" -d "API server" --json
Response:
{
"success": true,
"data": {
"port": 3000,
"projectName": "my-app",
"description": "API server",
"status": "reserved",
"reservedAt": "2024-01-20T10:30:00.000Z"
},
"message": "Port 3000 reserved successfully"
}
List Ports
portman list --json
Response:
{
"success": true,
"data": [
{
"number": 3000,
"projectName": "my-app",
"description": "API server",
"status": "reserved",
"pid": null,
"reservedAt": "2024-01-20T10:30:00.000Z",
"tags": ["backend", "api"]
}
]
}
Scan Active Ports
portman scan --json
Response:
{
"success": true,
"data": [
{
"port": 3000,
"pid": 12345,
"processName": "node",
"state": "LISTEN",
"address": "0.0.0.0",
"projectName": "my-app",
"isReserved": true
}
]
}
Request Multiple Ports
portman request 3 -n "microservices" --json
Response:
{
"success": true,
"data": {
"ports": [
{"number": 3000, "projectName": "microservices-1"},
{"number": 3001, "projectName": "microservices-2"},
{"number": 3002, "projectName": "microservices-3"}
],
"count": 3,
"method": "sequential"
}
}
🔧 Language Integration Examples
Node.js/JavaScript
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
class PortKeeper {
async checkPort(port) {
const { stdout } = await execPromise(`portman check ${port} --json`);
return JSON.parse(stdout);
}
async reservePort(port, project, description) {
const cmd = `portman reserve ${port} -n "${project}" -d "${description}" --json`;
const { stdout } = await execPromise(cmd);
return JSON.parse(stdout);
}
async listPorts(options = {}) {
let cmd = 'portman list --json';
if (options.project) cmd += ` -p "${options.project}"`;
if (options.status) cmd += ` -s ${options.status}`;
const { stdout } = await execPromise(cmd);
return JSON.parse(stdout);
}
async releasePort(port) {
const { stdout } = await execPromise(`portman release ${port} --json`);
return JSON.parse(stdout);
}
}
// Usage
const pk = new PortKeeper();
const result = await pk.checkPort(3000);
if (result.data.status === 'free') {
await pk.reservePort(3000, 'my-app', 'Development server');
}
Python
import subprocess
import json
class PortKeeper:
def run_command(self, cmd):
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return json.loads(result.stdout)
def check_port(self, port):
return self.run_command(f'portman check {port} --json')
def reserve_port(self, port, project, description=''):
cmd = f'portman reserve {port} -n "{project}"'
if description:
cmd += f' -d "{description}"'
cmd += ' --json'
return self.run_command(cmd)
def list_ports(self, project=None, status=None):
cmd = 'portman list --json'
if project:
cmd += f' -p "{project}"'
if status:
cmd += f' -s {status}'
return self.run_command(cmd)
def release_port(self, port):
return self.run_command(f'portman release {port} --json')
def request_ports(self, count, project, sequential=True):
cmd = f'portman request {count} -n "{project}"'
if sequential:
cmd += ' --sequential'
cmd += ' --json'
return self.run_command(cmd)
# Usage
pk = PortKeeper()
result = pk.check_port(3000)
if result['data']['status'] == 'free':
pk.reserve_port(3000, 'my-app', 'API server')
Ruby
require 'json'
require 'open3'
class PortKeeper
def run_command(cmd)
stdout, stderr, status = Open3.capture3(cmd)
JSON.parse(stdout)
end
def check_port(port)
run_command("portman check #{port} --json")
end
def reserve_port(port, project, description = nil)
cmd = "portman reserve #{port} -n \"#{project}\""
cmd += " -d \"#{description}\"" if description
cmd += " --json"
run_command(cmd)
end
def list_ports(project: nil, status: nil)
cmd = "portman list --json"
cmd += " -p \"#{project}\"" if project
cmd += " -s #{status}" if status
run_command(cmd)
end
def release_port(port)
run_command("portman release #{port} --json")
end
end
# Usage
pk = PortKeeper.new
result = pk.check_port(3000)
if result['data']['status'] == 'free'
pk.reserve_port(3000, 'my-app', 'Rails server')
end
Go
package main
import (
"encoding/json"
"fmt"
"os/exec"
)
type PortKeeper struct{}
type Response struct {
Success bool `json:"success"`
Data interface{} `json:"data"`
Message string `json:"message"`
Error string `json:"error"`
}
func (pk *PortKeeper) runCommand(command string) (*Response, error) {
cmd := exec.Command("sh", "-c", command)
output, err := cmd.Output()
if err != nil {
return nil, err
}
var response Response
err = json.Unmarshal(output, &response)
return &response, err
}
func (pk *PortKeeper) CheckPort(port int) (*Response, error) {
return pk.runCommand(fmt.Sprintf("portman check %d --json", port))
}
func (pk *PortKeeper) ReservePort(port int, project, description string) (*Response, error) {
cmd := fmt.Sprintf("portman reserve %d -n \"%s\"", port, project)
if description != "" {
cmd += fmt.Sprintf(" -d \"%s\"", description)
}
cmd += " --json"
return pk.runCommand(cmd)
}
// Usage
pk := &PortKeeper{}
resp, _ := pk.CheckPort(3000)
if resp.Success {
// Port check successful
}
Bash/Shell
#!/bin/bash
# Port management functions
check_port() {
portman check "$1" --json | jq -r '.data.status'
}
reserve_port() {
local port=$1
local project=$2
local desc=${3:-""}
if [ -n "$desc" ]; then
portman reserve "$port" -n "$project" -d "$desc" --json
else
portman reserve "$port" -n "$project" --json
fi
}
list_project_ports() {
portman list -p "$1" --json | jq -r '.data[].number'
}
release_project_ports() {
local ports=$(list_project_ports "$1")
if [ -n "$ports" ]; then
echo "$ports" | xargs portman release
fi
}
# Usage
if [ "$(check_port 3000)" = "free" ]; then
reserve_port 3000 "my-app" "Development server"
fi
🔄 Event Handling
Polling for Changes
// Poll for port status changes
async function monitorPort(port, interval = 5000) {
const pk = new PortKeeper();
let lastStatus = null;
setInterval(async () => {
const result = await pk.checkPort(port);
const currentStatus = result.data.status;
if (currentStatus !== lastStatus) {
console.log(`Port ${port} status changed: ${lastStatus} → ${currentStatus}`);
lastStatus = currentStatus;
// Handle status change
if (currentStatus === 'free') {
// Port became available
} else if (currentStatus === 'in-use') {
// Port is now in use
}
}
}, interval);
}
🏗️ Advanced Integration
Direct Node.js Module Usage
// Advanced: Direct usage (requires local installation)
import { PortService } from 'portkeeper/dist/core/services/PortService.js';
import { PortDatabase } from 'portkeeper/dist/core/database/Database.js';
const db = new PortDatabase();
const service = new PortService(db);
// Direct API calls
const ports = await service.getAllPorts();
const status = await service.checkPort(3000);
await service.reservePort(3000, 'my-app', 'API server', ['backend']);
🔐 Error Handling
Error Codes
PORT_INVALID
- Invalid port number (not 1-65535)PORT_RESERVED
- Port already reservedPORT_IN_USE
- Port actively in usePORT_NOT_FOUND
- Port not in databasePROJECT_REQUIRED
- Project name missingPERMISSION_DENIED
- Insufficient permissionsDATABASE_ERROR
- Database operation failed
Error Handling Example
async function safeReservePort(port, project) {
try {
const result = await pk.reservePort(port, project);
if (result.success) {
console.log(`Port ${port} reserved for ${project}`);
}
} catch (error) {
// CLI command failed
console.error('Command execution failed:', error);
}
}
// Check JSON response
const result = await pk.checkPort(3000);
if (!result.success) {
switch (result.code) {
case 'PORT_INVALID':
console.error('Invalid port number');
break;
case 'DATABASE_ERROR':
console.error('Database error:', result.error);
break;
default:
console.error('Unknown error:', result.error);
}
}
🚀 Best Practices
- Always check port status before reserving
- Handle errors gracefully - network/permission issues
- Use descriptive project names for team clarity
- Release ports when done to free resources
- Cache responses when appropriate
- Implement retries for transient failures
- Log operations for debugging
- Validate input before CLI calls
📊 Performance Considerations
Batch Operations
// Inefficient: Multiple calls
for (const port of ports) {
await pk.checkPort(port);
}
// Efficient: Single list call with filtering
const result = await pk.listPorts({ status: 'reserved' });
const reservedPorts = result.data.map(p => p.number);
Response Caching
class CachedPortKeeper extends PortKeeper {
constructor(cacheTime = 5000) {
super();
this.cache = new Map();
this.cacheTime = cacheTime;
}
async checkPort(port) {
const key = `check:${port}`;
const cached = this.cache.get(key);
if (cached && Date.now() - cached.time < this.cacheTime) {
return cached.data;
}
const result = await super.checkPort(port);
this.cache.set(key, { data: result, time: Date.now() });
return result;
}
}
🔮 Future API Features
Planned enhancements:
- WebSocket support for real-time updates
- REST API server mode
- GraphQL endpoint
- gRPC support
- SDK packages for major languages
For more examples and updates, check our GitHub repository or open an issue for feature requests.