# 🛑 Error Codes & Troubleshooting

TunnelSats uses standardized machine-readable error codes. Since our API involves real-world Lightning transactions, understanding these codes is the difference between a smooth automation and a broken one.

### 📊 Machine-Readable Error Codes

All error responses follow the `ApiError` schema: `{"error": "CODE", "message": "Reason"}`.

| Code | HTTP | Meaning | Mitigation |
| --- | --- | --- | --- |
| **`ERR_INVALID_INPUT`** | 400 | Invalid `serverId`, `duration`, or malformed pubkey. | Check your request body JSON syntax. |
| **`ERR_UNAUTHORIZED`** | 401 | Missing or invalid `Authorization` header. | Use `Bearer <api_key>` or `Nostr <token>`. |
| **`ERR_PAYMENT_REQUIRED`** | 402 | Subscription exists but hasn't been paid yet. | Poll the status until it returns `paid`. |
| **`ERR_MIGRATION_REQUIRED`** | 403 | Attempting to renew on a legacy server (us1). | Use the migration tool to move to us3. |
| **`ERR_RESOURCE_NOT_FOUND`** | 404 | No subscription found for that `paymentHash`. | Verify your hash from the `/create` call. |
| **`ERR_RATE_LIMIT_EXCEEDED`** | 429 | You've hit the rate limit. | Implement exponential backoff (Max 1 req/5s). |
| **`ERR_INTERNAL_ERROR`** | 500/503 | Backend node is syncing or maintenance mode. | Wait 30 seconds and try again. |

---

### 🔍 Specific Scenarios

<Tabs>
<TabItem value="expiry" label="Invoice Expiration">

:::warning [402 vs. 404]
If a user waits too long to pay, the invoice will expire.

* **Scenario:** You poll and keep getting `ERR_PAYMENT_REQUIRED`.
* **Expiry:** If the invoice expires, the `paymentHash` may eventually return `ERR_RESOURCE_NOT_FOUND`.
* **Action:** Generate a new subscription request via `/create`.
:::
</TabItem>
<TabItem value="ratelimit" label="Rate Limiting (429)">

:::danger [ERR_RATE_LIMIT_EXCEEDED]
Our public endpoints are rate-limited to ensure availability for all node operators.

* **Public IP Limit:** 60 reads/min.
* **Account Limit:** 30 reads/min.
* **Mitigation:** Implement an **exponential backoff**. If you hit a 429, wait 10 seconds before trying again.
:::
</TabItem>
<TabItem value="cloudflare" label="Development Envs (dev2)">

:::info [Cloudflare Access Bypass]
Requests to `dev2.tunnelsats.com` require Cloudflare Service Tokens.

* **Headers Required:**
  * `CF-Access-Client-Id`
  * `CF-Access-Client-Secret`
* **Failure:** Missing these will return a `302 Redirect` to a login HTML page.
:::
</TabItem>

</Tabs>

---

# 📝 Node Integration Checklist

For developers building integrations for **Umbrel**, **Start9**, or **RaspiBlitz**, follow this checklist to ensure a seamless user experience.

### 1. The Provisioning Flow

* [ ] **Server Selection:** Use `GET /api/public/v1/servers` to allow users to pick the region closest to them.
* [ ] **In-App Payment:** Display the BOLT11 invoice clearly.
* [ ] **Polling UI:** Progress indicator while polling `GET /api/public/v1/subscription/{paymentHash}`.

### 2. Configuration Management

* [ ] **WireGuard Claim:** Always offer a way to "Claim" the config again if the initial download fails.
* [ ] **Self-Healing:** If `wg show` indicates no handshake for >3 minutes, automatically call `/sync` or `/status` to verify the server hasn't migrated.

:::tip [Custom Keys]
We recommend generating keys on our server (leaving `wgPublicKey` empty) for the easiest setup. However, for maximum privacy, allow advanced users to provide their own `wgPublicKey`.
:::