ApiError schema: {"error": "CODE", "message": "Reason"}.| Code | HTTP | Meaning | Mitigation |
|---|---|---|---|
ERR_INVALID_INPUT | 400 | Invalid serverId, duration, malformed pubkey, or expired subscription. | Check request payload and verify subscription status. |
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, or to buy a bandwidth reset, on the legacy server (us1). | Use the migration tool to move to us3. |
ERR_FORBIDDEN | 403 | On /bandwidth-reset: the key was disabled for a reason other than its bandwidth limit (for example after a migration), so a reset would not restore it. | Don't retry; contact support. |
ERR_RESOURCE_NOT_FOUND | 404 | No subscription found for that paymentHash, or (on /status, /renew, /bandwidth-reset) every VPN server answered and none holds the key. | Verify your hash from the /create call, or the WireGuard public key. |
ERR_RATE_LIMIT_EXCEEDED | 429 | Rate limit exceeded or maximum monthly bandwidth resets reached (max 2/month). | Implement exponential backoff or wait for monthly reset counter to refresh. |
ERR_INTERNAL_ERROR | 500 | Internal server error. | Wait 30 seconds and retry. |
ERR_SERVICE_UNAVAILABLE | 503 | A VPN server could not be checked, so the key could be neither found nor ruled out (/status, /renew, /bandwidth-reset). Sent with a Retry-After header. | Retry after Retry-After seconds. Never treat it as "key unknown". |
503 | 503 | Backend VPN server unavailable or syncing (retryable). | Retry with exponential backoff. This is common during bandwidth-reset webhook processing if the VPN manager is temporarily unreachable. |
/api/public/*) and per-endpoint application limits:| Endpoint | Limit | Window | Key |
|---|---|---|---|
GET /servers | 60/min | 60s | Per IP |
POST /create | 10/min | 60s | Per IP |
POST /claim | 10/min | 60s | Per IP |
GET /{paymentHash} | 60/min | 60s | Per IP |
POST /status | 30/min | 60s | Per IP |
POST /renew | 20/min | 60s | Per IP |
POST /bandwidth-reset | 10/min | 60s | Per IP |
POST /{paymentHash}/sync | 5/min | 60s | Per subscription |
GET /list | 30/min | 60s | Per account |
POST /ping/lookup | 5/min | 60s | Per IP |
POST /ping/test | 3/min | 60s | Per IP |
ERR_PAYMENT_REQUIRED.paymentHash may eventually return ERR_RESOURCE_NOT_FOUND./create.