# 🛠️ Automation & Code Examples

This guide provides production-ready scripts to purchase, pay for, and provision a VPN tunnel programmatically using **TunnelSats**.

---

## ⚡ Quick Start Scripts

Choose your preferred environment to automate the VPN lifecycle.

<Tabs>
<TabItem value="python" label="Python (Requests)">

:::tip Recommended for Integrators
Use this script if you are building a dashboard, an auto-renewal bot, or integrating TunnelSats into node management software.
:::

### Prerequisites

```bash
pip install requests

```

### The "Full Lifecycle" Script

This script automates the **Create -> Pay -> Claim** flow and handles the current "Production Reality" of dual-key configurations.

```python
import requests
import time

# CONFIGURATION
# Best Practice: Use the absolute path prefix /api/public/v1/
API_BASE = "https://tunnelsats.com/api/public/v1"
SERVER_ID = "us-east"  
DURATION_MONTHS = 1

def buy_vpn():
    # 1. CREATE SUBSCRIPTION
    print(f"⚡ Requesting {DURATION_MONTHS}-month VPN in {SERVER_ID}...")
    response = requests.post(f"{API_BASE}/subscription/create", json={
        "serverId": SERVER_ID,
        "duration": DURATION_MONTHS
    })
    
    # Check for machine-readable error codes
    if not response.ok:
        error_json = response.json()
        if error_json.get('error') == 'ERR_RATE_LIMIT_EXCEEDED':
             print("⏳ Rate limited! Waiting 30s...")
             time.sleep(30)
             return buy_vpn()
        print(f"Error: {error_json.get('message')}")
        return

    data = response.json()
    payment_hash = data['paymentHash']
    invoice = data['invoice']

    print(f"\nPAY THIS INVOICE (Expires in 10 mins):\n{invoice}\n")

    # 2. POLL FOR PAYMENT
    print("⏳ Waiting for payment confirmation...", end="", flush=True)
    
    attempts = 0
    while attempts < 60: 
        time.sleep(5)
        # Link: [Check Status](https://api.tunnelsats.com/4063130e0.md)
        status_check = requests.get(f"{API_BASE}/subscription/{payment_hash}")
        
        if status_check.status_code == 200:
            if status_check.json()['status'] == 'paid':
                print("\n✅ Payment confirmed!")
                claim_vpn(payment_hash)
                return
        
        print(".", end="", flush=True)
        attempts += 1

def claim_vpn(payment_hash):
    # 3. CLAIM WIREGUARD CONFIG
    # Link: [Claim Subscription](https://api.tunnelsats.com/claim-a-wireguard-configuration-after-payment-4063131e0.md)
    claim_resp = requests.post(f"{API_BASE}/subscription/claim", json={
        "paymentHash": payment_hash
    })

    if claim_resp.status_code == 200:
        vpn_data = claim_resp.json()
        
        # PRODUCTION REALITY: Handle both 'fullConfig' or 'config' keys
        config_text = vpn_data.get('fullConfig') or vpn_data.get('config')
        
        if config_text:
            with open(f"tunnelsats-{SERVER_ID}.conf", "w") as f:
                f.write(config_text)
            print(f"🎉 Config saved to: tunnelsats-{SERVER_ID}.conf")
        else:
            print("❌ Error: Response missing configuration data.")

```

</TabItem>
<TabItem value="bash" label="Bash (cURL + jq)">

:::info Tooling Requirement
Ensure you have `curl` and `jq` installed on your system to run these commands.
:::

### 1. Get a Lightning Invoice

```bash
curl -s -X POST https://tunnelsats.com/api/public/v1/subscription/create \
  -H "Content-Type: application/json" \
  -d '{"serverId": "eu-de", "duration": 1}' | jq -r '.invoice'

```

### 2. Claim & Save Config (Safe Key Handling)

Once paid, run this to provision your WireGuard keys. We use `//` in `jq` to fallback between keys.

```bash
curl -s -X POST https://tunnelsats.com/api/public/v1/subscription/claim \
  -H "Content-Type: application/json" \
  -d '{"paymentHash": "<your_payment_hash>"}' | jq -r '.fullConfig // .config' > wg0.conf

```

</TabItem>
</Tabs>

---

## 💡 Important Considerations

:::warning Invoice Expiry
Lightning invoices and their associated `paymentHash` typically expire within **10 minutes**. If payment is not detected within this window, the polling script will timeout, and you must generate a new subscription request.
:::

:::note WireGuard Keys
In the examples above, we leave `wgPublicKey` empty in the `/claim` request. This tells the TunnelSats server to generate a fresh keypair for you. If you prefer to use your own keys, include your Public Key (Base64) in the request body.
:::

:::danger Case Sensitivity Reality
- **Status/Create/Renew:** Use `camelCase` (e.g., `paymentHash`, `subscriptionEnd`).
- **Subscription List:** Uses `snake_case` (e.g., `payment_hash`, `subscription_end`).
Always verify your JSON keys against the endpoint documentation.
:::