Articles in this section

STACKS API Guide

Published:

STACKS API Guide

Overview

The STACKS API lets you manage your containers programmatically. Use it to automate deployments, integrate with CI/CD pipelines, or give your AI agents access to infrastructure — all with built-in spending controls.

Base URL: https://api.stackshosting.cloud/api/v1


Authentication

All API requests require an API key passed in the Authorization header:

Authorization: Bearer sk_stacks_your_key_here

Creating an API Key

  1. Log in to your STACKS dashboard
  2. Go to Account Settings → API Keys
  3. Click Create New Key
  4. Enter a descriptive name (e.g., “CI/CD Pipeline”, “Agent Key”)
  5. Select the permissions (scopes) your key needs
  6. Optionally set a monthly spending cap
  7. Click Create API Key

Important: Your API key is shown only once at creation. Copy it immediately and store it securely. You will not be able to view it again.

API Key Format

STACKS API keys follow this format:

sk_stacks_{64_character_hex_string}

Supported Clients

The API accepts requests from any HTTP client including:

  • Node.js fetch / axios
  • Python requests, httpx, urllib
  • cURL
  • PowerShell Invoke-RestMethod
  • Any language or tool that can make HTTP requests

No special User-Agent or TLS configuration is required. Server-to-server automation clients are fully supported.

Security Best Practices

  • Never commit API keys to version control
  • Never share API keys in chat, email, or public channels
  • Use the minimum scopes required for your use case
  • Set spending caps on keys used by automated systems
  • Rotate keys regularly and revoke unused keys immediately
  • Use separate keys for different applications or environments

Permissions (Scopes)

Each API key has specific permissions that control what it can access:

Scope Description
containers:read List containers, view status, logs, credentials, metrics, and backups
containers:create Create new containers (subject to spending cap)
containers:manage Start, stop, restart containers, manage connections, external access, IP restrictions, and trigger backups
containers:delete Permanently delete containers

A key with only containers:read cannot create, modify, or delete containers. Attempting to use an endpoint that requires a scope your key doesn’t have will return a 403 Forbidden error.

What API Keys Cannot Access

API keys are restricted to container-related operations. The following endpoints require dashboard access (JWT authentication) and cannot be accessed via API key:

  • Billing and payment management
  • User account settings
  • API key management (create/revoke keys from the dashboard only)
  • Database browser
  • File manager
  • Container resize (billing implications)
  • SSH password changes

Spending Caps

When creating an API key, you can set a monthly spending cap. This limits the total cost of containers created through that key in a given calendar month.

  • If a create request would exceed the spending cap, the API returns 402 Payment Required with a clear error message
  • Spending caps are per-key, not per-account — different keys can have different limits
  • Spending resets at the beginning of each calendar month
  • Setting no spending cap allows unlimited creation (subject to account limits)

Recommended: Always set a spending cap on keys given to automated systems or AI agents.


Rate Limiting

Each API key has a configurable rate limit (default: 60 requests per minute).

Rate limit information is returned in response headers:

Header Description
X-RateLimit-Limit Maximum requests per minute
X-RateLimit-Remaining Requests remaining in current window
X-RateLimit-Reset Unix timestamp when the window resets

When rate limited, the API returns 429 Too Many Requests with a Retry-After header. Back off and retry after the specified delay.


Endpoints

List Containers

Retrieve all containers associated with your account.

GET /containers

Required scope: containers:read

Example:

curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers

PowerShell:

$headers = @{"Authorization"="Bearer sk_stacks_YOUR_KEY"}
Invoke-RestMethod -Uri "https://api.stackshosting.cloud/api/v1/containers" -Headers $headers

Response:

{
  "success": true,
  "count": 3,
  "data": [
    {
      "id": "usr-abc12345-myapp",
      "name": "myapp",
      "type": "nodejs",
      "plan": "basic",
      "status": "running",
      "cpu_allocated": 1,
      "memory_allocated": 1024,
      "storage_allocated": 10240,
      "ip_address": "10.52.0.5",
      "created_at": "2026-02-25T10:30:00.000Z",
      "raw_docker_status": "Up 2 hours (healthy)",
      "image_tag": "stackshosting/nodejs:latest",
      "version": "22",
      "version_major": "22"
    }
  ]
}

Get Container Status

Get detailed status for a specific container.

GET /containers/status/{container_id}

Required scope: containers:read

Example:

curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/status/usr-abc12345-myapp

Response:

{
  "success": true,
  "id": "usr-abc12345-myapp",
  "name": "myapp",
  "template": "nodejs",
  "type": "nodejs",
  "status": "running",
  "rawStatus": "Up 2 hours (healthy)",
  "ram": 1,
  "storage": 10240,
  "externalAccessEnabled": false,
  "ipAddress": "10.52.0.5",
  "version": "22",
  "versionMajor": "22",
  "plan": "basic",
  "cpuAllocated": "1.00",
  "memoryAllocated": 1024,
  "storageAllocated": 10240,
  "createdAt": "2026-02-25T10:30:00.000Z",
  "updatedAt": "2026-02-25T12:00:00.000Z"
}

Get Container Credentials

Retrieve connection credentials for a container.

GET /containers/{container_id}/credentials

Required scope: containers:read

Example:

curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/usr-abc12345-mydb/credentials

Response:

{
  "success": true,
  "credentials": [...],
  "data": {
    "containerName": "mydb",
    "type": "postgresql",
    "mainCredentials": {
      "username": "admin",
      "password": "generated-password"
    },
    "connectionInfo": {
      "host": "10.52.0.5",
      "port": 5432,
      "internalHost": "usr-abc12345-mydb"
    }
  }
}

Note: Returns SSH credentials for compute containers and database/web UI credentials for managed services. The connectionInfo.port is the service port (5432 for PostgreSQL, 6379 for Redis, etc.). Use internalHost for VPC connections between containers.


Get Container Logs

Retrieve recent logs from a container.

GET /containers/{container_id}/logs?lines=100

Required scope: containers:read

Parameters:

Parameter Type Default Description
lines integer 100 Number of log lines to return (max: 500)

Get Container Metrics

Retrieve CPU, memory, disk, and network metrics.

GET /containers/{container_id}/metrics

Required scope: containers:read

Response includes: CPU, memory, disk, network_in, and network_out metric arrays with timestamps.


Create Container

Create a new container. Container creation is asynchronous. The API returns immediately with the container ID and a pending status. Poll GET /containers/status/{id} to track progress until the status changes to running.

POST /containers

Required scope: containers:create

Request body:

{
  "name": "my-api-server",
  "template": "nodejs",
  "plan": "basic"
}
Field Required Description
name Yes Container name: 3-32 characters, letters, numbers, and hyphens. Must start and end with a letter or number. No consecutive hyphens.
template Yes Container template ID (see Available Templates below)
plan No micro, basic, standard, or premium (default: basic)
discount_code No Promotional discount code
additional_storage_tier No Additional storage: 1 (32GB), 3 (64GB), or 5 (128GB). Only for non-micro, non-database containers.

Name validation errors return 400 with field-level details:

{
  "success": false,
  "error": "Container name must be no more than 32 characters",
  "field": "name"
}

Example:

curl -X POST \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-api","template":"nodejs","plan":"basic"}' \
  https://api.stackshosting.cloud/api/v1/containers

Response (202 Accepted):

{
  "success": true,
  "message": "Container is being created. Poll GET /containers/status/{id} to track progress.",
  "data": {
    "id": "usr-abc12345-my-api",
    "name": "my-api",
    "template": "nodejs",
    "type": "nodejs",
    "plan": "basic",
    "status": "pending",
    "port": 8005,
    "cpu_allocated": 1,
    "memory_allocated": 1024,
    "storage_allocated": 10240,
    "createdAt": "2026-05-17T16:33:23.116Z",
    "hasWebUI": false,
    "sshUser": "usr-abc12345",
    "sshPort": 8005,
    "publicHost": "stacks.hosting",
    "hasSshAccess": true,
    "vpcNetwork": "vpc-usr-abc12345",
    "imageTag": "stackshosting/nodejs:latest",
    "version": "22",
    "versionMajor": "22"
  }
}

Polling for completion:

# Poll until status changes from "pending" to "running"
curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/status/usr-abc12345-my-api

Containers typically transition from pending to running within 10-30 seconds. If a container enters error status, check logs or contact support.


Start / Stop / Restart Container

Update a container’s running state.

PUT /containers/{container_id}/status

Required scope: containers:manage

Request body:

{
  "status": "running"
}
Value Action
running Start the container
stopped Stop the container
restart Restart the container

Delete Container

Permanently delete a container and all its data.

DELETE /containers/{container_id}

Required scope: containers:delete

Warning: This action is irreversible. All container data, volumes, and backups will be permanently removed.


Download SSH Certificate

Download the SSH private key for a container.

GET /containers/{container_id}/certificate

Required scope: containers:read

Note: Only available for compute containers (Ubuntu, Node.js, Python, etc.), not managed services.


Regenerate SSH Certificate

Generate a new SSH keypair for a container.

POST /containers/{container_id}/certificate/regenerate

Required scope: containers:manage


List Available Templates

Get all container templates with descriptions, categories, and pricing.

GET /containers/templates

Required scope: containers:read


External Access & IP Restrictions

Manage public access to your containers and control which IP addresses can connect.

Toggle External Access

Enable or disable public access for a container.

PUT /containers/{container_id}/external-access

Required scope: containers:manage

Request body:

{
  "enabled": true
}

Response (when enabling):

{
  "success": true,
  "message": "External access enabled. Your container is available at: myapp-ab1c2d.stackshosting.cloud",
  "your_ip": "203.0.113.45",
  "subdomain": "ab1c2d",
  "url": "myapp-ab1c2d.stackshosting.cloud"
}

When you enable external access, your current IP is automatically added to the allowed list. Disabling removes the subdomain and clears all IP restrictions.

List Allowed IPs

GET /containers/{container_id}/allowed-ips

Required scope: containers:read

Response:

{
  "success": true,
  "data": {
    "external_access_enabled": true,
    "subdomain": "ab1c2d",
    "url": "myapp-ab1c2d.stackshosting.cloud",
    "allowed_ips": [
      {
        "id": "ip-uuid-here",
        "ip_address": "203.0.113.45",
        "description": "Your IP (Auto-added)",
        "service_type": "both",
        "added_at": "2026-05-16T18:00:00.000Z"
      }
    ],
    "your_current_ip": "203.0.113.45"
  }
}

Add Allowed IP

POST /containers/{container_id}/allowed-ips

Required scope: containers:manage

Request body:

{
  "ip_address": "198.51.100.0/24",
  "description": "Production servers",
  "service_type": "both"
}
Field Required Description
ip_address Yes IPv4 or IPv6 address, with optional CIDR notation
description No Human-readable label (default: “Custom IP”)
service_type No http, tcp, or both (default: both)

Remove Allowed IP

DELETE /containers/{container_id}/allowed-ips/{ip_id}

Required scope: containers:manage

Note: You cannot remove the last IP address — this prevents accidental lockout.


Container Connections

Connect containers within your VPC network. Connections inject environment variables (host, port, credentials) into the source container so it can communicate with the target.

List Available Targets

Get containers that can be connected to.

GET /containers/{container_id}/available-targets

Required scope: containers:read

Response:

{
  "success": true,
  "data": {
    "databases": [
      {"id": "usr-abc12345-mydb", "name": "mydb", "type": "postgresql", "suggested": true}
    ],
    "caches": [
      {"id": "usr-abc12345-myredis", "name": "myredis", "type": "redis", "suggested": true}
    ],
    "loadBalancers": [],
    "applications": [],
    "all": [...]
  }
}

Create Connection

POST /containers/{source_id}/connect/{target_id}

Required scope: containers:manage

Request body (optional):

{
  "connectionName": "primary-db"
}

Response:

{
  "success": true,
  "data": {
    "id": "connection-uuid-here",
    "sourceContainer": "usr-abc12345-myapp",
    "targetContainer": "usr-abc12345-mydb",
    "connectionName": "primary-db",
    "connectionType": "postgresql",
    "environmentVariables": {
      "POSTGRES_HOST": "usr-abc12345-mydb",
      "POSTGRES_PORT": "5432",
      "POSTGRES_USER": "postgres",
      "POSTGRES_PASSWORD": "****",
      "POSTGRES_DB": "postgres"
    },
    "connectionString": "postgresql://postgres:****@usr-abc12345-mydb:5432/postgres",
    "message": "Connection created. Container will be updated with new environment variables.",
    "note": "You may need to restart the source container for changes to take effect."
  }
}

List Connections

GET /containers/{container_id}/connections

Required scope: containers:read

Response:

{
  "success": true,
  "data": {
    "outgoing": [
      {
        "id": "connection-uuid-here",
        "connection_name": "primary-db",
        "connection_type": "postgresql",
        "connection_string": "postgresql://postgres:****@usr-abc12345-mydb:5432/postgres",
        "created_at": "2026-05-16T18:00:00.000Z",
        "target_name": "mydb",
        "target_type": "postgresql",
        "target_status": "running"
      }
    ],
    "incoming": []
  }
}

Delete Connection

DELETE /containers/connections/{connection_id}

Required scope: containers:manage

Note: The path is /containers/connections/{id}, not /connections/{id}.


Backups

Daily automated backups run at 2:00 AM UTC with 7-day retention on replicated storage.

List Backups

GET /containers/{container_id}/backups

Required scope: containers:read

Parameters:

Parameter Type Default Description
limit integer 50 Number of backups to return (max: 100)

Response:

{
  "success": true,
  "data": [
    {
      "id": 178,
      "container_id": "usr-abc12345-mydb",
      "container_name": "mydb",
      "backup_type": "scheduled",
      "status": "completed",
      "snapshot_id": "fc26902e",
      "size_bytes": 746862,
      "backup_server": "cluster1host3",
      "created_at": "2026-05-17T02:00:14.000Z",
      "completed_at": "2026-05-17T02:00:16.000Z",
      "error_message": null,
      "pinned": 0
    }
  ]
}

Trigger Manual Backup

POST /containers/{container_id}/backup

Required scope: containers:manage

Container must be in running status. Backup runs in the background.

Response (202 Accepted):

{
  "success": true,
  "message": "Backup started in background",
  "data": {
    "container_id": "usr-abc12345-mydb",
    "status": "in_progress"
  }
}

Backup Statistics

Get backup stats across all your containers.

GET /backups/stats

Required scope: containers:read

Response:

{
  "success": true,
  "data": {
    "total_backups": 32,
    "completed_backups": "32",
    "failed_backups": "0",
    "total_size_bytes": "5933384",
    "last_backup_at": "2026-05-17T17:08:02.000Z"
  }
}

Restore Backup

POST /backups/{backup_id}/restore

Required scope: containers:manage

Container must be in running status.

Pin Backup

Protect a backup from automatic cleanup.

POST /backups/{backup_id}/pin

Required scope: containers:manage

Unpin Backup

Remove cleanup protection from a backup.

POST /backups/{backup_id}/unpin

Required scope: containers:manage

Delete Backup

DELETE /backups/{backup_id}

Required scope: containers:delete

Backup details:

  • Scheduled daily at 2:00 AM UTC
  • 7-day retention by default
  • Stored on replicated GlusterFS storage
  • Pin important backups to protect from auto-cleanup
  • Manual backups can be triggered at any time

VPN Management

Manage WireGuard VPN clients for STACKS VPN containers.

List VPN Clients

GET /containers/{vpn_container_id}/vpn-clients

Add VPN Client

POST /containers/{vpn_container_id}/vpn-clients

Request body:

{
  "name": "my-laptop"
}

Download VPN Config

GET /containers/{vpn_container_id}/vpn-config?client=default

Remove VPN Client

DELETE /containers/{vpn_container_id}/vpn-clients/{client_name}

Available Templates

Micro Tier ($1/month — 512MB RAM, 0.1 vCPU, 5GB storage)

Template ID Name Type Description
ubuntu-micro Ubuntu Micro System Lightweight Ubuntu 24.04
nginx-micro Nginx Micro Web Server Lightweight web server for static sites
redis-micro Redis Micro Database Redis 8.4 cache
pocketbase-micro PocketBase Micro Database PocketBase 0.37 realtime backend
rqlite-micro RQLite Micro SQL Database SQLite over HTTP API
storage-micro MinIO Micro Storage S3-compatible object storage
secrets-micro STACKS Secrets Infrastructure Encrypted secrets management
loadbalancer-micro Load Balancer Micro Infrastructure Simple load balancer (2-3 backends)
forgejo-micro Forgejo Micro Application Self-hosted Git server
picoclaw-micro PicoClaw Agent Micro Agent AI agent with persistent memory

Basic Tier ($4/month — 1 vCPU, 1GB RAM, 10GB storage)

Template ID Name Type Description
ubuntu Ubuntu System Ubuntu 24.04 LTS with SSH access
nodejs Node.js Runtime Node.js 22 LTS with PM2 process manager
python Python Runtime Python 3.14 with Flask and SSH access
postgresql PostgreSQL Database PostgreSQL 18 database server
mongodb MongoDB Database MongoDB 7.0 NoSQL database
mysql MySQL Database MySQL 8.4 database server
redis Redis Database Redis 8.4 in-memory data store
pocketbase PocketBase Database Open source backend with realtime database
wordpress WordPress Application WordPress 6.9 with PHP 8.3
ghost Ghost Application Professional publishing and blogging platform
forgejo Forgejo Application Self-hosted Git server with issues, PRs, and CI
php-apache PHP + Apache Application PHP 8.5 with Apache
nginx Nginx Web Server High-performance web server
minio MinIO Storage S3-compatible object storage
stacks-loadbalancer STACKS LoadBalancer Infrastructure Load balancer for distributing traffic
picoclaw PicoClaw Agent Agent AI agent with persistent memory and skills

Other Tiers

Template ID Tier Price Resources Description
stacks-vpn VPN $5/month 2GB RAM, 1 vCPU, 20GB Private VPN access to your container network

Container Plans

Plan CPU RAM Storage Price
micro 0.1 vCPU 512 MB 5 GB $1/month
basic 1 vCPU 1 GB 10 GB $4/month
standard 1 vCPU 2 GB 20 GB $5/month
premium 2 vCPU 4 GB 40 GB $10/month

All plans include VPC network isolation, per-minute billing (capped at monthly rate), and 3x data replication.

Additional Storage

Available for non-micro, non-database containers:

Tier Size Price
1 32 GB $1/month
3 64 GB $3/month
5 128 GB $5/month

Error Codes

Status Meaning
202 Accepted — request is being processed (container creation, backup trigger). Poll for status.
400 Bad request — check your request body and parameters. Validation errors include field and specific details.
401 Unauthorized — invalid or missing API key
402 Payment required — add a payment method or spending cap reached
403 Forbidden — insufficient scope or endpoint not available to API keys
404 Not found — container doesn’t exist or doesn’t belong to your account
429 Rate limited — check Retry-After header and slow down
500 Server error — contact support
503 No capacity — you’ve been added to the waitlist

Common Error Responses

Invalid API key:

{
  "error": "Invalid API key"
}

Insufficient scope:

{
  "error": "API key lacks required scope: containers:delete",
  "required_scope": "containers:delete",
  "key_scopes": ["containers:read", "containers:create"]
}

Spending cap exceeded:

{
  "error": "Monthly spending cap reached",
  "spending_cap": 10.00,
  "amount_spent": 10.00,
  "message": "Your API key spending cap of $10.00/month has been reached."
}

Name validation error:

{
  "success": false,
  "error": "Container name must be no more than 32 characters",
  "field": "name"
}

Endpoint blocked for API keys:

{
  "error": "This endpoint requires dashboard access (JWT authentication)",
  "hint": "This operation requires human approval via the dashboard."
}

Common Workflows

Deploy an Application

# 1. Create a Node.js container (returns 202 with status "pending")
curl -X POST \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-app","template":"nodejs","plan":"basic"}' \
  https://api.stackshosting.cloud/api/v1/containers

# 2. Poll status until "running"
curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/status/usr-YOUR_ID-my-app

# 3. Get SSH credentials
curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-my-app/credentials

# 4. Deploy your code via SSH using the returned credentials

Connect App to Database

# 1. Create a PostgreSQL database
curl -X POST \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-db","template":"postgresql","plan":"basic"}' \
  https://api.stackshosting.cloud/api/v1/containers

# 2. Wait for container to be running, then connect
curl -X POST \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"connectionName":"primary-db"}' \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-my-app/connect/usr-YOUR_ID-my-db

# 3. Restart your app to pick up the connection environment variables
curl -X PUT \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"restart"}' \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-my-app/status

Expose a Container Publicly with IP Restrictions

# 1. Enable external access
curl -X PUT \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true}' \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-mydb/external-access

# 2. Add additional allowed IPs
curl -X POST \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ip_address":"198.51.100.0/24","description":"Production servers","service_type":"both"}' \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-mydb/allowed-ips

# 3. List current restrictions
curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-mydb/allowed-ips

Backup Before a Risky Change

# 1. Trigger a manual backup
curl -X POST \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-mydb/backup

# 2. Verify it completed
curl -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/containers/usr-YOUR_ID-mydb/backups

# 3. Pin the backup so it won't be auto-cleaned
curl -X POST \
  -H "Authorization: Bearer sk_stacks_YOUR_KEY" \
  https://api.stackshosting.cloud/api/v1/backups/BACKUP_ID/pin

AI Agent Workflow

Give your AI agent an API key with a spending cap:

  1. Create an API key with containers:read, containers:create, containers:manage scopes and a $10/month spending cap
  2. Pass the key to your agent
  3. The agent can create containers, deploy code, and manage infrastructure
  4. If the spending cap is reached, the API rejects further container creation — your agent can’t run up the bill
  5. You get billed monthly as usual through your payment method

Revoking an API Key

  1. Go to Account Settings → API Keys
  2. Find the key you want to revoke
  3. Click the revoke button (X icon)
  4. Confirm the revocation

Revocation is immediate and permanent. Any system using the revoked key will instantly lose access. There is no way to un-revoke a key — create a new one instead.


Need Help?

Access denied
Access denied