# 🐳 Portainer API Management Guide

*Complete guide for managing homelab infrastructure via Portainer API*

## 📋 Overview

This guide covers how to interact with the Portainer API for managing the homelab infrastructure, including GitOps deployments, container management, and system monitoring.

## 🔗 API Access Information

### Primary Portainer Instance
- **URL**: https://192.168.0.200:9443
- **API Endpoint**: https://192.168.0.200:9443/api
- **Version**: 2.39.0 (Portainer Enterprise Edition)
- **Instance ID**: dc043e05-f486-476e-ada3-d19aaea0037d

### Authentication

Portainer supports two authentication methods:

**Option A — API Access Token (recommended):**
```bash
# Tokens starting with ptr_ use the X-API-Key header (NOT Bearer)
export PORTAINER_TOKEN="<your-portainer-api-token>"
curl -k -H "X-API-Key: $PORTAINER_TOKEN" https://192.168.0.200:9443/api/stacks
```

**Option B — JWT (username/password):**
```bash
TOKEN=$(curl -k -s -X POST https://192.168.0.200:9443/api/auth \
  -H "Content-Type: application/json" \
  -d '{"Username":"admin","Password":"YOUR_PASSWORD"}' | jq -r '.jwt')
curl -k -H "Authorization: Bearer $TOKEN" https://192.168.0.200:9443/api/stacks
```

> **Note:** `ptr_` API tokens must use `X-API-Key`, not `Authorization: Bearer`.
> Using `Bearer` with a `ptr_` token returns `{"message":"Invalid JWT token"}`.

### Endpoint IDs
| Endpoint | ID |
|---|---|
| Atlantis | 2 |
| Calypso | 443397 |
| Concord NUC | 443398 |
| Homelab VM | 443399 |
| RPi5 | 443395 |

## 🚀 GitOps Management

### Check GitOps Stack Status
```bash
# List all stacks with Git config
curl -k -s -H "X-API-Key: $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/stacks | \
  jq '[.[] | select(.GitConfig.URL) | {id:.Id, name:.Name, status:.Status, file:.GitConfig.ConfigFilePath, credId:.GitConfig.Authentication.GitCredentialID}]'

# Get specific stack details
curl -k -H "X-API-Key: $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/stacks/{stack_id}
```

### Trigger GitOps Deployment
```bash
# Redeploy stack from Git (pass creds inline to bypass saved credential cache)
curl -k -X PUT -H "X-API-Key: $PORTAINER_TOKEN" \
  -H "Content-Type: application/json" \
  "https://192.168.0.200:9443/api/stacks/{stack_id}/git/redeploy?endpointId={endpoint_id}" \
  -d '{"pullImage":true,"prune":false,"repositoryAuthentication":true,"repositoryUsername":"vish","repositoryPassword":"YOUR_GITEA_TOKEN"}'
```

### Manage Git Credentials
```bash
# The saved Git credential used by most stacks is "portainer-homelab" (credId: 1)
# List saved credentials:
curl -k -s -H "X-API-Key: $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/users/1/gitcredentials | jq '.'

# Update the saved credential (e.g. after rotating the Gitea token):
curl -k -s -X PUT \
  -H "X-API-Key: $PORTAINER_TOKEN" \
  -H "Content-Type: application/json" \
  "https://192.168.0.200:9443/api/users/1/gitcredentials/1" \
  -d '{"name":"portainer-homelab","username":"vish","password":"YOUR_NEW_GITEA_TOKEN"}'
```

### Scan Containers for Broken Credentials
```bash
# Useful after a sanitization commit — finds any REDACTED values in running container envs
python3 << 'EOF'
import json, urllib.request, ssl
ctx = ssl.create_default_context(); ctx.check_hostname = False; ctx.verify_mode = ssl.CERT_NONE
token = "REDACTED_TOKEN"
base = "https://192.168.0.200:9443/api"
endpoints = {"atlantis":2,"calypso":443397,"nuc":443398,"homelab":443399,"rpi5":443395}
def api(p):
    req = urllib.request.Request(f"{base}{p}", headers={"X-API-Key": token})
    with urllib.request.urlopen(req, context=ctx) as r: return json.loads(r.read())
for ep_name, ep_id in endpoints.items():
    for c in api(f"/endpoints/{ep_id}/docker/containers/json?all=true"):
        info = api(f"/endpoints/{ep_id}/docker/containers/{c['Id'][:12]}/json")
        hits = [e for e in (info.get("Config",{}).get("Env") or []) if "REDACTED" in e]
        if hits: print(f"[{ep_name}] {c['Names'][0]}"); [print(f"  {h}") for h in hits]
EOF
```

## 📊 Container Management

### List All Containers
```bash
# Get all containers across all endpoints
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/containers/json?all=true
```

### Container Health Checks
```bash
# Check container status
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/containers/{container_id}/json | \
  jq '.State.Health.Status'

# Get container logs
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/containers/{container_id}/logs?stdout=1&stderr=1&tail=100
```

## 🖥️ System Information

### Endpoint Status
```bash
# List all endpoints (servers)
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints

# Get system information
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/system/info
```

### Resource Usage
```bash
# Get system stats
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/system/df

# Container resource usage
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/containers/{container_id}/stats?stream=false
```

## 🔧 Automation Scripts

### Health Check Script
```bash
#!/bin/bash
# portainer-health-check.sh

PORTAINER_URL="https://192.168.0.200:9443"
TOKEN="$PORTAINER_TOKEN"

echo "🔍 Checking Portainer API status..."
STATUS=$(curl -k -s "$PORTAINER_URL/api/status" | jq -r '.Version')
echo "✅ Portainer Version: $STATUS"

echo "🐳 Checking container health..."
CONTAINERS=$(curl -k -s -H "Authorization: Bearer $TOKEN" \
  "$PORTAINER_URL/api/endpoints/1/docker/containers/json" | \
  jq -r '.[] | select(.State=="running") | .Names[0]' | wc -l)
echo "✅ Running containers: $CONTAINERS"

echo "📊 Checking GitOps stacks..."
STACKS=$(curl -k -s -H "Authorization: Bearer $TOKEN" \
  "$PORTAINER_URL/api/stacks" | \
  jq -r '.[] | select(.Status==1) | .Name' | wc -l)
echo "✅ Active stacks: $STACKS"
```

### GitOps Deployment Script
```bash
#!/bin/bash
# deploy-stack.sh

STACK_NAME="$1"
PORTAINER_URL="https://192.168.0.200:9443"
TOKEN="$PORTAINER_TOKEN"

if [[ -z "$STACK_NAME" ]]; then
    echo "Usage: $0 <stack_name>"
    exit 1
fi

echo "🚀 Deploying stack: $STACK_NAME"

# Find stack ID
STACK_ID=$(curl -k -s -H "Authorization: Bearer $TOKEN" \
  "$PORTAINER_URL/api/stacks" | \
  jq -r ".[] | select(.Name==\"$STACK_NAME\") | .Id")

if [[ -z "$STACK_ID" ]]; then
    echo "❌ Stack not found: $STACK_NAME"
    exit 1
fi

# Trigger redeploy
curl -k -X PUT -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  "$PORTAINER_URL/api/stacks/$STACK_ID/git/redeploy" \
  -d '{"RepositREDACTED_APP_PASSWORD":"main","PullImage":true}'

echo "✅ Deployment triggered for stack: $STACK_NAME"
```

## 📈 Monitoring Integration

### Prometheus Metrics
```bash
# Get Portainer metrics (if enabled)
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/containers/json | \
  jq '[.[] | {name: .Names[0], state: .State, status: .Status}]'
```

### Alerting Integration
```bash
# Check for unhealthy containers
UNHEALTHY=$(curl -k -s -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/endpoints/1/docker/containers/json | \
  jq -r '.[] | select(.State != "running") | .Names[0]')

if [[ -n "$UNHEALTHY" ]]; then
    echo "⚠️ Unhealthy containers detected:"
    echo "$UNHEALTHY"
fi
```

## 🔐 Security Best Practices

### API Token Management
- **Rotation**: Rotate API tokens regularly (monthly)
- **Scope**: Use least-privilege tokens when possible
- **Storage**: Store tokens securely (environment variables, secrets management)

### Network Security
- **TLS**: Always use HTTPS endpoints
- **Firewall**: Restrict API access to authorized networks
- **Monitoring**: Log all API access for security auditing

## 🚨 Troubleshooting

### Common Issues

#### Authentication Failures
```bash
# Check token validity
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/users/me
```

#### Connection Issues
```bash
# Test basic connectivity
curl -k -s https://192.168.0.200:9443/api/status

# Check certificate issues
openssl s_client -connect 192.168.0.200:9443 -servername atlantis.vish.local
```

#### GitOps Sync Issues
```bash
# Check stack deployment logs
curl -k -H "Authorization: Bearer $PORTAINER_TOKEN" \
  https://192.168.0.200:9443/api/stacks/{stack_id}/logs
```

## 📚 API Documentation

### Official Resources
- **Portainer API Docs**: https://docs.portainer.io/api/
- **Swagger UI**: https://192.168.0.200:9443/api/docs/
- **API Reference**: Available in Portainer web interface

### Useful Endpoints
- `/api/status` - System status
- `/api/endpoints` - Managed environments
- `/api/stacks` - GitOps stacks
- `/api/containers` - Container management
- `/api/images` - Image management
- `/api/volumes` - Volume management
- `/api/networks` - Network management

## 🔄 Integration with Homelab

### GitOps Workflow
1. **Code Change**: Update compose files in Git repository
2. **Webhook**: Git webhook triggers Portainer sync (optional)
3. **Deployment**: Portainer pulls changes and redeploys
4. **Verification**: API checks confirm successful deployment

### Monitoring Integration
- **Health Checks**: Regular API calls to verify system health
- **Metrics Collection**: Export container metrics to Prometheus
- **Alerting**: Trigger alerts on deployment failures or container issues

---

**Last Updated**: February 14, 2026  
**Portainer Version**: 2.33.7  
**API Version**: Compatible with Portainer EE  
**Status**: ✅ Active and Operational