# Examples

Complete scripts for the common jobs, including a standard server, a web server in a VPC, nightly snapshots and a clean teardown.

Source: https://docs.pantechdynamics.com/api/examples

Each script runs as it is with `bash`, `curl` and `jq`, and a key in `PANTECH_API_KEY` (a write key, for anything that creates). Start every script with these helpers:

```sh title="pantech.sh"
#!/usr/bin/env bash
set -euo pipefail

API="https://api.pantechdynamics.com/public/v1"

# pt METHOD PATH [JSON]: call the API. Writes get a fresh Idempotency-Key.
pt() {
  local method=$1 path=$2 body=${3:-}
  local args=(-sS --fail-with-body -X "$method" "$API$path" -H "Authorization: Bearer $PANTECH_API_KEY")
  if [ "$method" != GET ]; then args+=(-H "Idempotency-Key: $(uuidgen)"); fi
  if [ -n "$body" ]; then args+=(-H "Content-Type: application/json" -d "$body"); fi
  curl "${args[@]}"
}

# wait_op OPERATION_ID: wait for an operation to finish; fail if it failed.
wait_op() {
  local status
  while :; do
    status=$(pt GET "/operations/$1" | jq -r .status)
    case "$status" in
      succeeded) return 0 ;;
      failed) pt GET "/operations/$1" | jq .failure >&2; return 1 ;;
    esac
    sleep 3
  done
}
```

:::info[Retrying safely]
`pt` makes a new `Idempotency-Key` per call, which is right for a script run once. If you retry a failed call yourself, reuse its key so the change cannot happen twice.
:::

## A standard server

A VPS with its own public IPv4 address, reachable over SSH with your key.

```sh
source pantech.sh

# Your public key, registered once.
KEY_ID=$(pt POST /ssh-keys "$(jq -n --arg k "$(cat ~/.ssh/id_ed25519.pub)" '{name: "laptop", public_key: $k}')" | jq -r .id)

# Order the instance. It is paid for, then built.
ORDER=$(pt POST /instances "$(jq -n --arg key "$KEY_ID" \
  '{name: "app-1", plan_slug: "starter", image_slug: "ubuntu-24-04", ssh_key_id: $key}')")
ORDER_ID=$(echo "$ORDER" | jq -r .order_id)
VM=$(echo "$ORDER" | jq -r .instance_id)

# Wait for the order to finish.
while :; do
  STATUS=$(pt GET "/instance-orders/$ORDER_ID" | jq -r .status)
  case "$STATUS" in
    provisioned) break ;;
    failed|payment_failed) echo "order $STATUS" >&2; exit 1 ;;
  esac
  sleep 5
done

IP=$(pt GET "/instances/$VM" | jq -r .public_ipv4)
echo "ssh ubuntu@$IP"   # the image's user: ubuntu on Ubuntu, debian on Debian
```

The default security group drops all inbound traffic. Open SSH to your own address with a [security group](https://docs.pantechdynamics.com/api/security-groups) rule, or ask for [temporary SSH access](https://docs.pantechdynamics.com/api/instances).

## A web server in a VPC

A private network with one subnet, a firewall that lets HTTPS in, an instance in the subnet, and a public IP mapped to it.

```sh
source pantech.sh

# 1. The VPC and a subnet. Wait for each before using it.
OP=$(pt POST /networks '{"name": "prod", "cidr": "10.0.0.0/16", "region": "af-abj"}')
NET=$(echo "$OP" | jq -r .resource_id); wait_op "$(echo "$OP" | jq -r .operation_id)"

OP=$(pt POST "/networks/$NET/subnets" '{"name": "web", "cidr": "10.0.1.0/24"}')
SUBNET=$(echo "$OP" | jq -r .resource_id); wait_op "$(echo "$OP" | jq -r .operation_id)"

# 2. Let HTTPS in. Lower numbers are checked first.
OP=$(pt POST "/subnets/$SUBNET/firewall-rules" \
  '{"number": 100, "protocol": "tcp", "port_start": 443, "cidr": "0.0.0.0/0"}')
wait_op "$(echo "$OP" | jq -r .operation_id)"

# 3. An instance in the subnet.
ORDER_ID=$(pt POST /instances "$(jq -n --arg s "$SUBNET" --arg k "$KEY_ID" \
  '{name: "web-1", plan_slug: "developer", image_slug: "ubuntu-24-04", subnet_id: $s, ssh_key_id: $k}')" | jq -r .order_id)
until [ "$(pt GET "/instance-orders/$ORDER_ID" | jq -r .status)" = provisioned ]; do sleep 5; done
VM=$(pt GET "/instance-orders/$ORDER_ID" | jq -r .instance_id)

# 4. A public IP mapped to it.
OP=$(pt POST /public-ips "$(jq -n --arg n "$NET" --arg vm "$VM" '{network_id: $n, instance_id: $vm, purpose: "static_nat"}')")
PIP=$(echo "$OP" | jq -r .resource_id); wait_op "$(echo "$OP" | jq -r .operation_id)"

echo "https://$(pt GET "/public-ips/$PIP" | jq -r .address)"
```

## Nightly snapshots

Let the platform do it: a schedule takes a snapshot every day and keeps the newest seven.

```sh
source pantech.sh
pt PUT "/instances/$VM/snapshot-schedule" '{"frequency": "daily", "retention_count": 7}'
```

Or take one yourself before a risky change, and roll back if it goes wrong:

```sh
source pantech.sh
OP=$(pt POST "/instances/$VM/snapshots" '{"name": "pre-upgrade"}')
SNAP=$(echo "$OP" | jq -r .resource_id); wait_op "$(echo "$OP" | jq -r .operation_id)"

# … the upgrade went wrong. Everything written since the snapshot is lost:
NAME=$(pt GET "/instances/$VM" | jq -r .name)
pt POST "/instances/$VM/restore" "$(jq -n --arg s "$SNAP" --arg n "$NAME" '{snapshot_id: $s, confirm_name: $n}')"
```

## Find what costs the most

A read key is enough.

```sh
source pantech.sh
pt GET "/usage/by-resource?period=month" \
  | jq -r '.data | sort_by(-.cost_minor) | .[:5][] | "\(.cost_minor / 100)\t\(.resource_kind)\t\(.resource_name)"'
```

## Tear everything down

Delete in the reverse of creation: what uses a thing goes before the thing.

```sh
source pantech.sh
wait_op "$(pt DELETE "/public-ips/$PIP" | jq -r .operation_id)"
wait_op "$(pt DELETE "/instances/$VM" | jq -r .operation_id)"
wait_op "$(pt DELETE "/subnets/$SUBNET" | jq -r .operation_id)"
wait_op "$(pt DELETE "/networks/$NET" | jq -r .operation_id)"
```

Deleting an instance keeps a safety snapshot of its root disk for 24 hours. Volumes attached to it are detached, not deleted, so delete them separately.

## Walk every page of a list

```sh
source pantech.sh
CURSOR=""
while :; do
  PAGE=$(pt GET "/instances?limit=100${CURSOR:+&cursor=$CURSOR}")
  echo "$PAGE" | jq -r '.data[] | "\(.id)\t\(.observed_state)\t\(.name)"'
  CURSOR=$(echo "$PAGE" | jq -r '.next_cursor // empty')
  [ -z "$CURSOR" ] && break
done
```
